4 条题解
-
1
你们要的 代码来了!!!
#include<bits/stdc++.h> using namespace std; #define int long long const int N=2e6+10; int n,id,tot,ch[N][2],fa[N]; vector<int>ed[N]; void ins(string s,int x) { int p=0,len=s.size(); for(int i=0;i<len;i++) { int j=s[i]-'0'; if(!ch[p][j])ch[p][j]=++id,fa[id]=p; p=ch[p][j]; } ed[p].push_back(x); } vector<int>G[N]; int dfn[N],low[N],scc[N],tsp,cnt; stack<int>stk;bool instk[N]; void tarjan(int x) { dfn[x]=low[x]=++tsp; stk.push(x);instk[x]=1; for(int y:G[x]) { if(dfn[y]==0) { tarjan(y); low[x]=min(low[x],low[y]); } else if(instk[y])low[x]=min(low[x],dfn[y]); } if(dfn[x]==low[x]) { cnt++; for(int z=-1;z!=x;) { z=stk.top();stk.pop();instk[z]=0; scc[z]=cnt; } } } map<string,int>mp; signed main() { cin>>n; for(int i=1;i<=n;i++) { string s;cin>>s;mp[s]++; if(mp[s]>2) { cout<<"NO"<<'\n'; return 0; } int pos=s.find('?'); if(pos==string::npos)ins(s,i*2),ins(s,i*2+1); else s[pos]='0',ins(s,i*2),s[pos]='1',ins(s,i*2+1); } tot=n*2+2; for(int i=0;i<=id;i++) { for(int j=0;j<2;j++)if(ch[i][j]) { G[tot+i*2].push_back(tot+ch[i][j]*2); G[tot+ch[i][j]*2+1].push_back(tot+i*2+1); } for(int x:ed[i]) { G[tot+i*2].push_back(x^1); G[tot+i*2+1].push_back(x^1); G[x].push_back(tot+fa[i]*2+1); for(int j=0;j<2;j++)if(ch[i][j]) G[x].push_back(tot+ch[i][j]*2); } } tot+=id*2+1; for(int i=0;i<=id;i++) for(int j:ed[i])for(int k:ed[i]) if(j!=k)G[j].push_back(k^1); for(int i=1;i<=tot;i++)if(!dfn[i])tarjan(i); for(int i=1;i<=n;i++)if(scc[i*2]==scc[i*2+1]) { cout<<"NO"<<'\n'; return 0; } cout<<"YES"<<'\n'; return 0; } -
0
这份代码使用了 “Trie 树辅助节点(入点/出点)优化建图” 的方法,将前缀冲突转化为 2-SAT 约束。下面我将结合代码,详细解释其核心思路,并给出严格证明。
一、核心思路:将“前缀冲突”转化为“图上的路径”
1. 问题本质
给定 个字符串,每个最多有一个
?。问能否为每个?填0或1,使得所有字符串互不为前缀。- 变量:每个字符串 有两种状态(选
0或选1),对应 2-SAT 中的布尔变量。 - 约束:如果字符串 的某种形态是字符串 的某种形态的前缀,则这两个状态不能同时为真。
2. Trie 树上的前缀关系
将所有可能的字符串插入 Trie 树:
- 若字符串 是 的前缀,则 的结束节点是 路径上的祖先。
- 若 和 完全相同,则它们结束在同一个节点。
因此,前缀冲突 = Trie 树上的祖先-后代关系(或同节点关系)。
二、代码中的建图规则
1. 节点编号
- 字符串变量:第 个字符串选
0为 ,选1为 。 - Trie 辅助节点:每个 Trie 节点 拆成两个辅助节点:
- 入点 :表示“有字符串经过 且其后代被选中”,向下传递。
- 出点 :表示“有字符串在 的某个后代结束”,向上传递。
2. 连边规则(代码对应)
// 1. Trie 树内部传递 add(in(u), in(v)); // u 是 v 的父亲:入点向下传递 add(out(v), out(u)); // u 是 v 的父亲:出点向上传递 // 2. 字符串与辅助节点交互(x 是结束在 u 的字符串变量) add(in(u), x ^ 1); // 若 u 的入点被激活,则 x 必须为假 add(out(u), x ^ 1); // 若 u 的出点被激活,则 x 必须为假 add(x, out(fa(u))); // 若 x 为真,则激活 u 的所有真祖先的出点 add(x, in(v)); // 若 x 为真,则激活 u 的所有真后代的入点 // 3. 同节点互斥(多个字符串结束在同一个节点) add(x, y ^ 1); // x 和 y 不能同时为真 add(y, x ^ 1);
三、正确性证明
我们需要证明:存在合法赋值(构成前缀编码) 2-SAT 图存在合法解。
引理:前缀冲突的 Trie 树刻画
设字符串 结束在节点 ,字符串 结束在节点 。
- 是 的前缀 是 的祖先(或 )。
证明方向 1:若两个字符串冲突,则图中必存在互斥约束
情况 1: 是 的真前缀( 是 的真祖先)
目标:证明 且 。
-
若 为真(变量 为真):
- 代码连边 ,其中 是 的儿子且在 的路径上。
- 通过入点链 ,激活 的结束节点 的入点。
- 代码连边 ( 是 的变量)。
- 因此 。
-
若 为真(变量 为真):
- 代码连边 ,通过出点链向上传递,最终激活 。
- 代码连边 。
- 因此 。
结论: 和 不能同时为真,冲突被正确建模。
情况 2: 和 完全相同(结束在同节点 )
- 代码中通过同节点互斥边直接连 和 。
- 因此 和 不能同时为真。
证明方向 2:若图中存在互斥约束,则两个字符串必冲突
我们需要证明建图不会引入虚假冲突(即不会把不冲突的字符串连成互斥)。
分析每条边的语义:
- :若 的入点被激活(有字符串经过 且其后代被选中),则 的入点也应被激活。这是合理的传递。
- :若 的出点被激活(有字符串在 的后代结束),则 的出点也应被激活。这是合理的传递。
- :若 在 结束,则其真祖先的出点被激活。这意味着真祖先不能作为其他字符串的结束节点,否则会冲突。
- :若 在 结束,则其真后代的入点被激活。这意味着真后代不能作为其他字符串的前缀经过,否则会冲突。
- :若 被占用(有字符串经过或在后代结束),则以 结束的字符串 不能为真。这正是前缀冲突的定义。
结论:所有连边都严格对应前缀冲突,不会引入额外约束。
证明方向 3:2-SAT 判定等价性
- 若存在合法赋值(构成前缀编码),则对于图中每条边 ,若 为真则 必为真(由方向 2 保证)。因此 2-SAT 有解。
- 若 2-SAT 有解,则任意两个冲突的字符串不会同时为真(由方向 1 保证)。因此字符串集合构成前缀编码。
四、为什么 而不是 ?
如果连 ,则:
这会导致 ,即 必须为假,显然错误。
因此, 只能激活其真祖先的出点(),避免自我否定。同理, 只能激活其真后代的入点( 的儿子),而不是 本身。
五、总结
这份代码的核心思路是:
- 将前缀关系转化为 Trie 树上的祖先-后代关系。
- 用入点/出点辅助节点实现链式传递,避免 建边。
- 通过严格的连边规则,确保“冲突 互斥”。
- 用 Tarjan 判 2-SAT 是否有解。
该方法的正确性由上述三个方向的证明保证,是解决此类“树形结构优化 2-SAT 建图”问题的标准范式。
- 变量:每个字符串 有两种状态(选
-
0
你说得对,但我的错解冲过去了(后面我加了hack)
#include<bits/stdc++.h> using namespace std; typedef long long ll; int n,n1,n0; string ss[500010]; int ch[1000010][2],ed[1000010],fl=1,id,tg[1000010],ted[1000010],vis[500010][2],s0[500010],s1[500010],tag[1000010]; vector<int> v[1000010][2],v1; void ins0(string s){ int p=0; for(int i=0;i<s.size();i++){ int j=s[i]-'0'; if(!ch[p][j])ch[p][j]=++id; p=ch[p][j]; tg[p]++; if(ted[p])fl=0; for(int pp:v[p][0]){ if(!vis[pp][0]){ if(vis[pp][1]){ fl=0; return ; } for(int i=0;i<ss[pp].size();i++)if(ss[pp][i]=='?')ss[pp][i]='0'; v1.push_back(pp); vis[pp][0]=1; } } for(int pp:v[p][1]){ if(!vis[pp][1]){ if(vis[pp][0]){ fl=0; return ; } for(int i=0;i<ss[pp].size();i++)if(ss[pp][i]=='?')ss[pp][i]='1'; v1.push_back(pp); vis[pp][1]=1; } } if(!fl)return ; } if(tg[p]>1){ fl=0; } ted[p]++; } void ins1(string s,int x){ int p1=0,p2=0,k=s.size(); for(int i=0;i<s.size();i++)if(s[i]=='?')k=i; for(int i=0;i<k;i++){ int j=s[i]-'0'; if(!ch[p1][j])ch[p1][j]=++id; p1=ch[p1][j]; tg[p1]++; } if(k==s.size()-1){ tag[p1]++; if(tag[p1]>2){ fl=0; return ; } } p2=p1; if(!ch[p1][0])ch[p1][0]=++id; if(!ch[p2][1])ch[p2][1]=++id; p1=ch[p1][0];p2=ch[p2][1]; for(int i=k+1;i<s.size();i++){ int j=s[i]-'0'; if(!ch[p1][j])ch[p1][j]=++id; if(!ch[p2][j])ch[p2][j]=++id; p1=ch[p1][j]; p2=ch[p2][j]; } if(tg[p1]&&tg[p2]){ fl=0; } v[p1][0].push_back(x); v[p2][1].push_back(x); } int main(){ ios::sync_with_stdio(0); cin.tie(0); cin>>n; for(int i=1;i<=n;i++){ cin>>ss[i]; int ffll=0; for(char j:ss[i]){ if(j=='?'){ ffll=1; break; } } if(ffll)s1[++n1]=i; else s0[++n0]=i; } for(int i=1;i<=n1;i++){ ins1(ss[s1[i]],s1[i]); } memset(tag,0,sizeof(tag)); for(int i=1;i<=n1;i++){ ins1(ss[s1[i]],s1[i]); } for(int i=1;i<=n0;i++){ ins0(ss[s0[i]]); } for(int i=0;i<v1.size();i++){ ins0(ss[v1[i]]); if(!fl)break; } cout<<(fl?"YES\n":"NO\n"); return 0; } /* hack1: 6 ?111 ?01 ?100 ?1 10 0010? NO hack2: 11 000101010 0?100 1?011 000000 001100?10 10110000? 0?10 111?0010 ?1 ?1 1101110?1 NO */ -
0

#include <bits/stdc++.h> using namespace std; const int N = 3e6 + 5; int n, k, len[N], dfn[N], low[N], tim, scc[N], cnt, ch[N][2], id[N][2], tot, st[N], top; bool ins[N]; vector<int> e[N]; string s[N]; void Add(int x, int y) { e[x].emplace_back(y); } void Tarjan(int x) { dfn[x] = low[x] = ++tim, ins[x] = 1, st[++top] = x; for (int y : e[x]) { if (!dfn[y]) { Tarjan(y), low[x] = min(low[x], low[y]); } else if (ins[y]) { low[x] = min(low[x], dfn[y]); } } if (dfn[x] == low[x]) { ++cnt; do { scc[st[top]] = cnt, ins[st[top]] = 0; } while (st[top--] != x); } } void Insert(string &s, int n, int p, int q) { int x = 0; for (int i = 1; i <= n; ++i) { int c = s[i] - '0'; if (!ch[x][c]) { ch[x][c] = ++tot; } x = ch[x][c]; if (id[x][1]) { Add(p, id[x][1]), Add(id[x][0], q); } } ++k; if (id[x][1]) { Add(k, id[x][1]); } Add(k, q), id[x][1] = k; ++k; if (id[x][0]) { Add(id[x][0], k); } Add(p, k), id[x][0] = k; } int main() { ios::sync_with_stdio(0); cin.tie(0), cout.tie(0); cin >> n, k = n << 1; for (int i = 1; i <= n; ++i) { cin >> s[i]; s[i] = " " + s[i]; } sort(s + 1, s + n + 1, [](string &i, string &j) { return i.size() < j.size(); }); for (int i = 1; i <= n; ++i) { int m = s[i].size() - 1, pos = 0; for (int j = 1; j <= m; ++j) { if (s[i][j] == '?') { pos = j; break; } } s[i][pos] = '0', Insert(s[i], m, i, i + n); s[i][pos] = '1', Insert(s[i], m, i + n, i); } for (int i = 1; i <= n << 1; ++i) { if (!dfn[i]) { Tarjan(i); } } for (int i = 1; i <= n; ++i) { if (scc[i] == scc[i + n]) { cout << "NO"; return 0; } } cout << "YES"; return 0; }
- 1
信息
- ID
- 10094
- 时间
- 2000ms
- 内存
- 512MiB
- 难度
- 9
- 标签
- 递交数
- 38
- 已通过
- 3
- 上传者