Codeforces Round #721 (Div. 2)
题目:https://codeforc.es/contest/1527
A
题意:给定数字n。求m。使n&(n-1)&(n-2)&...&(m)=0,m是可能的最大值。
题解:转化为二进制可以发现,如果想要每一位都是0,那么必须有一个数最高位是0。设最高位为0,其他位为1的数为x,那么必然存在x到n之间的数使某个位&后为0。
所以题目转化为求x。
#include<iostream> #include<algorithm> #include<vector> #include<map> #include<cmath> #include<cstring> using namespace std; const int mx=50+10; const int inf=1<<30; typedef long long ll; ll two[mx]; void init(){ two[0]=1; for(ll i=1;i<=32;i++){ two[i]=two[i-1]*2; } } void solve(){ init(); ll t; scanf("%lld", &t); for(ll i=1;i<=t;i++){ ll v; scanf("%lld", &v); ll num =0; while(v!=0){ v=v>>=1; num++; } printf("%lld\n", two[num-1]-1); } } int main(){ solve(); return 0; }
B1. Palindrome Game (easy version)
题意:给定一个长为n的对称01串。alice和bob两个人轮流操作。如果当前状态为对称,那么必须选一个0位改为1;如果不对称,可以选一个0位改为1,也可以reverse一下,但是如果上一次操作对方以及reverse过了,那此次就不能reverse。
题解:首先,reverse对于最终结果没用,直接看成跳过操作。其次,由于是对称情况,所以串里面的1字符也没用。如果原来的串有x个0y个1,那么可以把原串看成x个0的串。
接下来分情况讨论:
如果x为偶数:
从x=2开始看,先手alice必须走一步(因为是对称),走完一步后局面不对称,bob可以跳过,alice不得不走下一步,游戏结束。此时先手alice比后手bob多走了两步。后手bob赢。
接着看x>2的情况。对于局面x,alice必须走一步,bob可以走alice对称的位子,从而局面转化为了x-2的对称局面;接着alice不得不因为对称继续走一步,bob走对称位子,局面转化为x-4;...;
以此类推,一直持续到局面2,此时alice走了之后bob可以不走。因此最后局面是 alice比bob多走两步。后手bob赢。
如果x为奇数:
从x=1开始看,先手alice走一步,结束;alice输;
从x>3开始看,先手alice必走一步,走哪一步?:
如果走正中间mid位置,局面又转化为了没结束对称局面,此时对称下先手为bob,后手为alice。那么最终alice多走了开始的第一步,但是对称局面下可以少走两步。最终情况alice少走一步。alice赢。
总结:
x为偶数,后手必赢
x为奇数。如果x为1,后手必赢;反之先手必赢。
#include<iostream> #include<algorithm> #include<vector> #include<map> #include<cmath> #include<cstring> using namespace std; const int mx=1e3+10; const int inf=1<<30; typedef long long ll; void solve(){ ll n; scanf("%lld", &n); for(ll i=1;i<=n;i++){ ll v; scanf("%lld", &v); char s[mx]; scanf("%s", s); for(ll j=0;j<strlen(s);j++){ if(s[j]=='1')v--; } if(v&1){ if(v == 1){ printf("BOB\n"); } else{ printf("ALICE\n"); } } else{ printf("BOB\n"); } } } int main(){ solve(); return 0; }
B2. Palindrome Game (hard version)
题意:B1上加了条件 开局可能不对称
题解:
离谱,上次写的没保存。。。不想再写一遍了。
也是分奇偶。
偶数n。先手必赢。
奇数n。只有一种情况:正中间的值为0,然后存在其他一个位置的值为0。此时平局,其他情况先手必赢。
#include<iostream> #include<algorithm> #include<vector> #include<map> #include<cmath> #include<cstring> using namespace std; const int mx=1e3+10; const int inf=1<<30; typedef long long ll; const int mx2=30; char alice[mx2]="ALICE"; char bob[mx2]="BOB"; char draw[mx2]="DRAW"; ll n, v, num; char ch[mx]; ll duichen(ll len){//对称情况下 如果先手必胜 返回true 反之返回false if(len&1){//奇数 //如果不为1 先手必胜 至少 少走1步 return len == 1?-1:1; } return -1;//偶数情况下 后手 bob必胜 至少 少走2步 } ll chulijishu(){//0 平局 ll res=0;//对称的0的个数 也就是对称局面剩下的步数 //只要res不为0 对称的后手可以比先手少走两步 ll need =0;//need代表 实现对称 至少需要多少步数(不考虑正中间 ll mid=(v-1)/2; for(ll i=0, j=v-1;i<j;i++, j--){ if(ch[i]!=ch[j]) need++; if(ch[i]==ch[j] && ch[i]=='0') res+=2; } if(ch[mid] == '0'){ res++; } if(need == 0){//如果局面对称 return duichen(res); } //局面不对称 剩下的 也没有对称的情况 if(res == 0){//alice躺赢 return 1; } //局面不对称 剩下的也有对称的情况 //1: need == 1 res == 1 if(need == 1 && res == 1){ //res == 1 说明是 mid位置 有一个0 //need == 1 说明其他位置 还有一个0 return 0;//平局 } return 1; // //2: need != 1 res == 1 // else if( need != 1 && res == 1){ // //res == 1 说明是 mid位置 有一个0 // //最后两步 肯定是 一个mid 一个其他 平局 所以看之前的步数决定 // //因为need!=1 所以b的步数肯定大于a // return 1;//a躺赢 // } // //3: need == 1 res != 1 // else if(need == 1 && res != 1){ // //need == 1 说明除了mid 其他位置px 还有一个0 // //a走了px 构成对称 res!=1 那么 b必走2步 // //最后a少走一步 // return 1; // } // //4: need != 1 res != 1 // else if(need != 1 && res != 1){ // //a可以一直躺到对称前 那么最差的情况是 b走了一步 a也走了一步 // //res!=1 b被迫走两步 // return 1; // } } ll chulioushu(){ ll res=0;//对称的0的个数 也就是对称局面剩下的步数 //只要res不为0 对称的后手可以比先手少走两步 ll need =0;//need代表 实现对称 需要多少步数(和奇数不一样 //v代表总长度 for(ll i=0, j=v-1;i<j;i++, j--){ if(ch[i]!=ch[j]) need++; if(ch[i]==ch[j] && ch[i]=='0') res+=2; } if(need == 0){//如果局面对称 return duichen(res); } //局面必然是不对称的 if(res == 0){//说明不存在对称的部分 alice可以躺赢 return 1; } //如果存在对称的部分 alice就需要去抢 对称前的 前一步 //alice 至少可以少 2-1 = 1步 所以alice必赢 return 1; } void solve(){ scanf("%lld", &n); for(ll i=1;i<=n;i++){ num=0; scanf("%lld", &v); scanf("%s", ch); ll num; if(v&1){ num = chulijishu(); } else{ num = chulioushu(); } if(num == 1)printf("%s\n", alice); else if(num == -1) printf("%s\n", bob); else printf("%s\n", draw); } } int main(){ solve(); return 0; }
C. Sequence Pair Weight
题意:一个长为n的串A。对于一个子串a,里面的a[i]=a[j]的ij对数(i<j)为这个子串的weight。求这个串A的所有子串的weight之和。
题解:
把题目拆为求每种数字对结果的贡献。
以1211为例,先看1给最后结果带来的贡献。把从左到右的三个1称为一号1二号2三号1
从右往左看,三号1为i的贡献为0。(注意 i<j)
再看二号1,以二号1开头,它可以和三号1构成最多贡献1(因为三号1之后没有其他位置了)。对于以二号1左边的开始的串,每个串可能有的二号1和三号1的组合贡献也是1。所以二号1为i的贡献为3*1。3为包括二号1在内的二号1之前的开头的可能,1为二号1开头作为i带来的贡献。
同理看一号1。以一号1开头,可以和三号1构成最多贡献1(因为三号1之后没有其他位置了)。可以和二号1构成最多贡献2(因为包括二号1及以后一共两个位置)。那么以二号1开头作为i的贡献是1+2=3;而包括一号1在内的开头的可能只有一种。所以一号1作为i带来的贡献是1*(1+2);
然后整理下,可以发现每个1作为i带来贡献是(包括当前1位置及之前的位置的个数)*(当前1位置作为开头和右边的1的贡献和)。
(包括当前1位置及之前的位置的个数)很容易求得。
而(当前1位置作为开头和右边的1的贡献和)可以发现
三号号1贡献和0
二号1贡献和2
一号1贡献和1+2
。。
其实可以发现,i号1的贡献和是i+1的贡献和加上 (n-(i+1)号1的位置+1)
然后就可以算答案了。
绕死我了这题。。= =。
#include<iostream> #include<algorithm> #include<vector> #include<map> #include<cmath> #include<cstring> using namespace std; const int mx=1e5+10; const int inf=1<<30; typedef long long ll; ll num, v; void solve(){ ll n; scanf("%lld", &n); for(ll t=1;t<=n;t++){ scanf("%lld", &num); map<ll,ll>mp;//mp[i]表示数字i的最右边的位置是啥 ll bef[mx];//bef[i]表示i位置上的数字的左边的第一个相同数字位置是啥 // memset(bef, 0, sizeof(bef)); for(ll i=1;i<=num;i++){ scanf("%lld", &v); bef[i]=mp[v]; mp[v]=i; } map<ll,ll>::iterator it; ll ans=0; for(it=mp.begin();it!=mp.end();it++){ ll val=it->first; ll rig=it->second;//当前数的右边第一个相同数位置 ll cur=bef[rig];//右边开始数第二个位置 ll tot=0;//目前为止 和右边开始数第一个、第二个。。。可以构成的数量 while(cur!=0){ //cur是当前位置 tot=tot+(num-rig+1); //处理一下带来的增量 ans+=tot*cur; rig=cur; cur=bef[cur]; } } printf("%lld\n", ans); } } int main(){ solve(); return 0; }
D. MEX Tree
题意:给定一个n个节点的树,节点值为0到n-1各不重复。每一次求节点i和节点j的对数,使i到j的最短路径上的节点值的mex值为k。一共求k=0~n的对应对数。
题解:
https://codeforc.es/blog/entry/90939
直接翻官方题解了。。。555不会。
。