4 条题解

  • 1
    @ 2026-8-12 21:02:04

    你们要的 4n4n 代码来了!!!

    #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
      @ 2026-8-13 8:45:37

      ProvedProved byby Qwen:Qwen:

      这份代码使用了 “Trie 树辅助节点(入点/出点)优化建图” 的方法,将前缀冲突转化为 2-SAT 约束。下面我将结合代码,详细解释其核心思路,并给出严格证明。


      一、核心思路:将“前缀冲突”转化为“图上的路径”

      1. 问题本质

      给定 nn 个字符串,每个最多有一个 ?。问能否为每个 ?01,使得所有字符串互不为前缀

      • 变量:每个字符串 ii 有两种状态(选 0 或选 1),对应 2-SAT 中的布尔变量。
      • 约束:如果字符串 AA 的某种形态是字符串 BB 的某种形态的前缀,则这两个状态不能同时为真

      2. Trie 树上的前缀关系

      将所有可能的字符串插入 Trie 树:

      • 若字符串 AABB 的前缀,则 AA 的结束节点是 BB 路径上的祖先
      • AABB 完全相同,则它们结束在同一个节点

      因此,前缀冲突 = Trie 树上的祖先-后代关系(或同节点关系)


      二、代码中的建图规则

      1. 节点编号

      • 字符串变量:第 ii 个字符串选 02i2i,选 12i+12i+1
      • Trie 辅助节点:每个 Trie 节点 uu 拆成两个辅助节点:
        • 入点 in(u)=tot+2uin(u) = \text{tot} + 2u:表示“有字符串经过 uu 且其后代被选中”,向下传递
        • 出点 out(u)=tot+2u+1out(u) = \text{tot} + 2u+1:表示“有字符串在 uu 的某个后代结束”,向上传递

      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);
      

      三、正确性证明

      我们需要证明:存在合法赋值(构成前缀编码)     \iff 2-SAT 图存在合法解

      引理:前缀冲突的 Trie 树刻画

      设字符串 AA 结束在节点 uu,字符串 BB 结束在节点 vv

      • AABB 的前缀     \iff uuvv 的祖先(或 u=vu = v)。

      证明方向 1:若两个字符串冲突,则图中必存在互斥约束

      情况 1:AABB 的真前缀(uuvv 的真祖先)

      目标:证明 A¬BA \to \neg BB¬AB \to \neg A

      • AA 为真(变量 xx 为真):

        • 代码连边 xin(v)x \to in(v'),其中 vv'uu 的儿子且在 BB 的路径上。
        • 通过入点链 in(v)in(v)in(v') \to in(v),激活 BB 的结束节点 vv 的入点。
        • 代码连边 in(v)¬yin(v) \to \neg yyyBB 的变量)。
        • 因此 A¬BA \to \neg B
      • BB 为真(变量 yy 为真):

        • 代码连边 yout(fa(v))y \to out(fa(v)),通过出点链向上传递,最终激活 out(u)out(u)
        • 代码连边 out(u)¬xout(u) \to \neg x
        • 因此 B¬AB \to \neg A

      结论AABB 不能同时为真,冲突被正确建模。

      情况 2:AABB 完全相同(结束在同节点 uu

      • 代码中通过同节点互斥边直接连 x¬yx \to \neg yy¬xy \to \neg x
      • 因此 AABB 不能同时为真。

      证明方向 2:若图中存在互斥约束,则两个字符串必冲突

      我们需要证明建图不会引入虚假冲突(即不会把不冲突的字符串连成互斥)。

      分析每条边的语义:

      1. in(u)in(v)in(u) \to in(v):若 uu 的入点被激活(有字符串经过 uu 且其后代被选中),则 vv 的入点也应被激活。这是合理的传递。
      2. out(v)out(u)out(v) \to out(u):若 vv 的出点被激活(有字符串在 vv 的后代结束),则 uu 的出点也应被激活。这是合理的传递。
      3. xout(fa(u))x \to out(fa(u)):若 xxuu 结束,则其真祖先的出点被激活。这意味着真祖先不能作为其他字符串的结束节点,否则会冲突。
      4. xin(v)x \to in(v):若 xxuu 结束,则其真后代的入点被激活。这意味着真后代不能作为其他字符串的前缀经过,否则会冲突。
      5. in(u)/out(u)¬xin(u)/out(u) \to \neg x:若 uu 被占用(有字符串经过或在后代结束),则以 uu 结束的字符串 xx 不能为真。这正是前缀冲突的定义。

      结论:所有连边都严格对应前缀冲突,不会引入额外约束。


      证明方向 3:2-SAT 判定等价性

      • 若存在合法赋值(构成前缀编码),则对于图中每条边 aba \to b,若 aa 为真则 bb 必为真(由方向 2 保证)。因此 2-SAT 有解。
      • 若 2-SAT 有解,则任意两个冲突的字符串不会同时为真(由方向 1 保证)。因此字符串集合构成前缀编码。

      四、为什么 xout(fa(u))x \to out(fa(u)) 而不是 xout(u)x \to out(u)

      如果连 xout(u)x \to out(u),则:

      xout(u)¬xx \to out(u) \to \neg x

      这会导致 x¬xx \to \neg x,即 xx 必须为假,显然错误。

      因此,xx 只能激活其真祖先的出点(fa(u)fa(u)),避免自我否定。同理,xx 只能激活其真后代的入点(uu 的儿子),而不是 uu 本身。


      五、总结

      这份代码的核心思路是:

      1. 将前缀关系转化为 Trie 树上的祖先-后代关系
      2. 用入点/出点辅助节点实现链式传递,避免 O(N2)O(N^2) 建边。
      3. 通过严格的连边规则,确保“冲突     \iff 互斥”
      4. 用 Tarjan 判 2-SAT 是否有解

      该方法的正确性由上述三个方向的证明保证,是解决此类“树形结构优化 2-SAT 建图”问题的标准范式。

      • 0
        @ 2026-8-12 16:40:22

        你说得对,但我的错解冲过去了(后面我加了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
          @ 2026-8-12 15:47:40

          #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
          上传者