Codeforces Round #815 (Div. 2) (补题中)
战绩:
打到一半被叫走,回来后断断续续打完的。。。
A. Burenka Plays with Fractions
刚开始感觉被trick绕进去了,思路有点乱,就先去切B了。
实际上如果要a/b=c/d,我们只用判断a*d和b*c的关系就好。
注意判断0的情况。

int main() { cin>>T; while(T--) { cin>>a>>b>>c>>d; ll lima=a*d; ll limb=b*c; if(lima>limb) swap(lima,limb); if(lima==limb) cout<<0<<endl; else if(lima==0||limb==0) cout<<1<<endl; else if(limb%lima==0) cout<<1<<endl; else cout<<2<<endl; } return 0; }
B. Interesting Sum
一开始没仔细读题,因为A没切出来,B就有些急。
实际上我们无论选那个区间,都能够选出最大次大最小次小四个数字。
排序计算即可。

int main() { read(T); while(T--) { read(n); for(int i=1;i<=n;i++) read(a[i]); sort(a+1,a+1+n); cout<<a[n]+a[n-1]-a[1]-a[2]<<endl; } return 0; }
C. Corners
只要出现了这样的0,它周围八格存在另一个0,那么有多少个1就是答案。
否则如果是单独的0,答案是1的个数-1.
否则如果没有0,个数-2.

int main() { cin>>T; while(T--) { cin>>n>>m; getchar(); for(int i=1;i<=n;i++) { for(int j=1;j<=m;j++) { a[i][j]=getchar(); } getchar(); } bool flag=0,flag1=0; ll tot=0; for(int i=1;i<=n;i++) { for(int j=1;j<=m;j++) { if(a[i][j]=='0') flag1=1; else tot++; if(((a[i][j]==a[i][j-1])&&(j!=1))||((a[i][j]==a[i][j+1])&&(j!=m))||((a[i][j]==a[i+1][j])&&(i!=n))||((a[i][j]==a[i-1][j])&&(i!=1)) ||(a[i][j]==a[i+1][j+1]&&(i!=n&&j!=m))||(a[i][j]==a[i-1][j+1]&&(i!=1&&j!=m))||(a[i][j]==a[i+1][j-1]&&(i!=n&&j!=1))||(a[i][j]==a[i-1][j-1]&&(i!=1&&j!=1))) { if(a[i][j]=='0') flag=1; } } } if(flag) cout<<tot<<endl; else if(flag1) cout<<tot-1<<endl; else cout<<tot-2<<endl; } return 0; }
D1. Xor-Subsequence (easy version)
最基础的LIS问题可以用O(n2)的两个for循环完成,我们注意到这个关系式
(a[j]^i)<(a[i]^j)
以及a[i]的数据范围是小于200的,也就是说每个a的二进制都不超过8位,那么这个式子里两边的每个a,都只能够影响i或者j的后面8位,只要i和j的第一个差别位置在前面,那么再往前面的就没影响了。
也就是i和j的差别大于256的时候,这个关系式一定不成立。
注意位运算优先级

int main() { read(T); while(T--) { read(n); for(int i=0;i<n;i++) {read(a[i]);dp[i]=1;} ll ans=0; for(int i=0;i<n;i++) { for(int j=i-1;j>=max(0,i-256);j--) { if((a[j]^i)<(a[i]^j)) dp[i]=max(dp[i],dp[j]+1); } ans=max(dp[i],ans); } cout<<ans<<endl; } return 0; }
D2. Xor-Subsequence (hard version)(字典树DP)
思路来自严鸽鸽的知乎https://zhuanlan.zhihu.com/p/555425330
自己推理了一下,确实感到十分巧妙,十分感谢。
由于变化的是a数组的大小范围,我们很难去延续简单版本的思路,要考虑发现一些新的性质。
注意看这个关系式(a[j]^i)<(a[i]^j),假设左右两边的前k位都相同,第k+1位不同,那么在第k+1位有a[i]^j==1并且a[j]^i==0.
那么我们可以推出a[i]^j^a[j]^i==1,移项有a[i]^i^1==a[j]^j
但是单纯满足a[i]^i^1==a[j]^j不能表示(a[j]^i)<(a[i]^j),也就是说两者并不是充要条件的关系,因此我们还要让a[i]≠j(以上都是在第k+1为的情况)
我们使用字典树去维护这样一个东西,每一个节点代表的是a[i]^i,同时每个节点开两个数组,分别记录a[i]^i到了这个节点的同时i的在这一位是0或者1的最大DP值。
然后我们整理一下思路:
我们每次用a[i]^i对字典树进行查找,每一位我们都可以在树上找一个位相反的位置节点(存在这样的节点时,满足条件(a[i]^i^1==a[j]^j)),然后在这个节点上会有两个值,分别是j的值为0或者为1的时候,这一位保存的最大DP值,我们要满足a[i]≠j,就要取a[i]^1的位置的值进行更新。(查找答案的函数)
当我们更新完当前的a[i]^i的时候,我们要带着下标i将a[i]^i挂在树上,用下标更新经过的每个节点的这一位的DP值。(更新的函数)
这一题确实巧妙的使用了异或的性质,将难以操作的表达式,按位用两个易于操作的表达式分解。
更详细的解释可以去看严鸽鸽的知乎。

inline void insert(ll lim,ll posa) { int note=0; for(int i=32;i>=0;i--) { int nxt=(lim>>i)&1; if(!Tr[note][nxt]) Tr[note][nxt]=++tot; note=Tr[note][nxt]; //我们每次对这一位的i的状态进行更新. F[note][(posa>>i)&1]=max(F[note][(posa>>i)&1],dp[posa]); } } inline ll looking(ll lim,ll a) { ll anslim=1; int note=0; for(int i=32;i>=0;i--) { int nxt=(lim>>i)&1; int too=Tr[note][nxt^1]; //在这一位转入另一边,此时too的节点记录着之前的a[j]^j与自己不同的位置 //此时满足a[i]^i^1==a[j]^j anslim=max(anslim,F[too][(a>>i)&1^1]+1); //此时满足a[i]≠j,F的第二维存入的是下标的这一位数字。 if(Tr[note][nxt]!=0) note=Tr[note][nxt]; else break; } return anslim; } int main() { read(T); while(T--) { for(int i=0;i<=tot;i++) Tr[i][0]=Tr[i][1]=F[i][0]=F[i][1]=0; tot=1; //保持一致 read(n); for(int i=0;i<n;i++) { dp[i]=1; read(a[i]); ll lim=a[i]^i; //在字典树上查找的值 dp[i]=looking(lim,a[i]); insert(a[i]^i,i); } ll ans=0; for(int i=0;i<n;i++) ans=max(ans,dp[i]); cout<<ans<<"\n"; } return 0; }