P1942 词编码_NOI导刊 by HydraNazis

· · 题解

P1942 词编码_NOI导刊

题目描述

长度为 n01 串满足是 1 位置号总和整除(n+1),而且可以进行以下四种操作:

1$ . 将一个 $0$ 变成 $1 2$ . 删去一个 $0$ 或 $1 3$ . 插入一个 $0$ 或 $1 现给你多个**变换后**的 $01$ 串,需要你求出变换前的 $01$ 串 有以下规定: 多解情况,以 $4$ 操作优先 $1$,$2$,$3$ 操作次之。 同一操作,以从左到右顺序优先。 操作 $2$ ,以删 $0$ 优先。 无解,输出$-1

分析

本题暴力分较高,无论用 sring+STL 还是 char 数组+模拟来维护,都躲不过 操作2 , 3的一次循环,还有模拟修改位置的一次循环,总复杂度为O(数据组数\times n^2),1000的数据范围可以过 80 分。
所以我们有两种思路,一种是通过数学观察,避免真正地进行操作,另一种是降低插入和删除的操作复杂度(经过实测,套上平衡树板子,因为常数过大而数据范围太小只过了40分,千万不要尝试)。

解答

$~~~~~~~$针对操作 $4$,直接计算出题目要求的有 $1$ 位置号和(以下称要求值),而且要满足长度要求,判断一下即可。 $~~~~~~~$针对操作 $2$ 和 $3$,我们发现一旦添加或删去一个字符,整个串的要求值会改变,而暴力的思想就是强行重算。我们仔细观察,其实这两个操作只是改变了选择操作位置之后所有 $1$ 的位置号,从而改变要求值。 $~~~~~~~$解释完后,一些同学已经明白该怎么搞了,但是不知道代码具体怎么实现的可以继续看看。 $~~~~~~~$我们考虑对每组数据维护一个前缀和 $pre$ (准确来说是后缀和,反正是相同的),记录从位置 $1$ 到位置 $i$ 有多少个 $1$ 出现,我们就知道修改位置后有多少个 $1$。 $~~~~~~~$针对 $2$ 的逆过程,选择添加的位置以后,每有一个 $1$ ,就会使操作前的的要求值加上$1$,最后根据添的是 $1$ 或 $0$ 加上这一位的数。 $~~~~~~~$针对 $3$ 的逆过程,删去的原理也是一样的。 $~~~~~~~$针对 $1$ 的逆过程,换成 $0$ 改变的是这一位,在原来的要求值上加上选择的位置值。 $~~~~~~~$看证明: $~~~~~~~sum \mod (n+1)=0~~~~~~~~~~~~~~~~~~~~~$正向操作时满足 $~~~~~~~(sum+x) \mod (n+1)=x\ne 0~~~~$操作后满足 $~~~~~~~$而且$1\leq x\leq n $~~~~~~~$这已经说的不能再详细了,看暴力和$AC$代码吧 $~~~~~~~$两份代码中的 $STL$ 十分实用,特别是当你写暴力的时候,简直爽。 $~~~~~~~s.insert(pos,string\ t)~~~~~~$在 $s$ 的 $pos$ 位置**后**插入 $t ```cpp inline bool check(string str) { int len=str.length(),sum=0; for(int i=0;i<=len-1;i++) if(str[i]=='1')sum+=i+1; if(sum%(n+1)==0||sum==0)return true; return false; } inline string solve(string str)//你懂得,就很暴力。 { int len=str.length(); string test=str; if(len==n&&check(test))return str; if(len==n) { for(int i=0;i<=len-1;i++) { test[i]='0'; if(check(test))return test; test=str; } return "-1"; } if(len<n) { for(int i=0;i<=len;i++) { test.insert(i,"0"); if(check(test))return test; test=str; test.insert(i,"1"); if(check(test))return test; test=str; } return "-1"; } if(len>n) { for(int i=0;i<=len-1;i++) { test.erase(i,1); if(check(test))return test; test=str; } return "-1"; } } int main() { string s; n=read(); while(cin>>s)cout<<solve(s)<<endl; return 0; } ``` 然后上 $AC$ 代码 ```cpp int n,sum,cnt,pre[N],x; string s; inline string solve(string str) { sum=cnt=x=0; memset(pre,0,sizeof(pre)); int len=str.length(); if(len<n-1&&len>n+1)return "-1";//长度错误。 for(int i=0;i<=len-1;i++) { if(str[i]=='1')cnt++,sum+=i+1,pre[i+1]=pre[i]+1; else pre[i+1]=pre[i];//cnt->1的个数,sum->要求值,pre->前缀和,有多少个1. } x=sum%(n+1); if(len==n) { if(!x||!sum)return str; else if(str[x-1]=='1'){str[x-1]='0';return str;}//如以上所说。 return "-1"; } if(len<n) { for(int i=0;i<=len;i++) if((sum+(cnt-pre[i]))%(n+1)==0){str.insert(i,"0");return str;}//题目要求0优先,两个循环一定不要写在一起,否则··· for(int i=0;i<=len;i++) if((sum+(cnt-pre[i])+i+1)%(n+1)==0){str.insert(i,"1");return str;} return "-1"; } if(len>n) { for(int i=0;i<=len-1;i++) { if((sum-(cnt-pre[i+1]))%(n+1)==0&&str[i]=='0'){str.erase(i,1);return str;}//一定要带上str【i】的条件,否则··· if((sum-(cnt-pre[i+1])-i-1)%(n+1)==0&&str[i]=='1'){str.erase(i,1);return str;} } return "-1"; } } int main() { n=read(); while(cin>>s)cout<<solve(s)<<endl; return 0; }//完美谢幕。 ``` ## 总结 说大实话,这其实不太能称得上提高组的题目,暴力分太多,优化很好想,但是对于题目的理解**十!分!重!要!** 题面给的十分毒瘤,没有明显表明多组数据,还有不明所以的句子。 最重要的一点,$string$ 用的是真的爽。