Codeforces 1017 Div4(ABCDEFG)
前言
补题补题补题
一、A. Trippi Troppi
#include <bits/stdc++.h>
using namespace std;typedef long long ll;void solve()
{for(int i=0;i<3;i++){string s;cin>>s;cout<<s[0];}cout<<endl;
}int main()
{ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);int t=1;cin>>t;while(t--){solve(); }return 0;
}
签到题,每次输出第一个字符即可,没啥好说的。
二、B. Bobritto Bandito
#include <bits/stdc++.h>
using namespace std;typedef long long ll;void solve()
{int n,m,l,r;cin>>n>>m>>l>>r;for(int i=n;i>m;i--){if(l<0){l++;}else {r--;}}cout<<l<<" "<<r<<endl;
}int main()
{ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);int t=1;cin>>t;while(t--){solve(); }return 0;
}
因为给出一种可能性即可,所以只要从后往前推,左边界小于零就算左侧的,否则就算右边界的即可。
三、C. Brr Brrr Patapim
#include <bits/stdc++.h>
using namespace std;typedef long long ll;void solve()
{int n;cin>>n;vector<vector<int>>grid(n+1,vector<int>(n+1));vector<bool>vis((n<<1)+1);//注意数字的范围是2n!!!for(int i=1;i<=n;i++){for(int j=1;j<=n;j++){cin>>grid[i][j];vis[grid[i][j]]=true;}}for(int i=1;i<=(n<<1);i++){if(!vis[i]){cout<<i<<" ";break;}}for(int j=1;j<=n;j++){cout<<grid[1][j]<<" ";}for(int i=2;i<=n;i++){cout<<grid[i][n]<<" ";}cout<<endl;
}int main()
{ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);int t=1;cin>>t;while(t--){solve(); }return 0;
}
观察可得整个矩阵是对角线对称的,所以先统计出第0号数,再沿着边界摸一遍即可。
四、D. Tung Tung Sahur
#include <bits/stdc++.h>
using namespace std;typedef long long ll;string p,s;
int n,m;//TLE错解
bool dfs(int i,int j)
{if(i>=n&&j<m){return false;}if(i<n&&j>=m){return false;}if(i==n&&j==m){return true;}bool ans=false;if(p[i]==s[j]){ans=ans||dfs(i+1,j+1);if(p[i]==s[j+1]){ans=ans||dfs(i+1,j+2);}}return ans;
}bool ways1()
{return dfs(0,0);
}bool ways2()
{if(n>2*m){return false;}int j=0;for(int i=0,cntp,cnts;i<n;i++,j++){if(p[i]!=s[j]){return false;}if(j>=m){return false;}cntp=1;cnts=1;while(i+1<n&&p[i]==p[i+1]){i++;cntp++;}while(j+1<m&&s[j]==s[j+1]){j++;cnts++;}if(cnts>2*cntp||cnts<cntp){return false;}}//边界讨论!!!!!!!!!!if(j<m){return false;}return true;
}string randomString(int n)
{string str;for(int i=0,tmp;i<n;i++){tmp=rand()%2;str+=(tmp?"L":"R");}return str;
}void solve()
{cin>>p>>s;n=p.length();m=s.length();//对数器// srand(time(0));// int testTime=10000;// for(int i=0;i<testTime;i++)// {// int n=rand()%10000+1;// int m=rand()%20000+1;// p=randomString(n);// s=randomString(m);// bool ans1=ways1();// bool ans2=ways2();// if(ans1!=ans2)// {// cout<<"Wrong!"<<endl;// cout<<p<<endl<<s<<endl;// }// }// cout<<"Over"<<endl;cout<<(ways2()?"YES":"NO")<<endl;
}int main()
{ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);int t=1;cin>>t;while(t--){solve(); }return 0;
}
ways1就是双指针暴力递归,然后喜提TLE。
然后再观察两个字符串的特征,可以发现可以根据p串里听到的声音在s串中分组,那么对于任意一组,听到的声音必然全和敲击的位置相同,且听到的次数必然介于一倍敲击次数到两倍敲击次数之前。所以那就设两个指针,然后进行分组滑动即可。重点要注意这些边界讨论,因为这个爆了两次WA……
五、E. Boneca Ambalabu
#include <bits/stdc++.h>
using namespace std;//每一位相互独立 -> 拆位计算,最后计算总贡献typedef long long ll;void solve()
{int n;cin>>n;vector<int>nums(n);vector<int>cnts(32,0);for(int i=0;i<n;i++){cin>>nums[i];for(int j=0;j<32;j++){cnts[j]+=((nums[i]>>j)&1);//统计这位上有几个1}}ll ans=0;for(int i=0;i<n;i++){ll sum=0;for(int j=0;j<32;j++){int bit=(nums[i]>>j)&1;//计算贡献if(bit==0){sum+=cnts[j]*((ll)1<<j);//开long long!!!}else{sum+=(n-cnts[j])*((ll)1<<j);//开long long!!!}}ans=max(ans,sum);}cout<<ans<<endl;
}int main()
{ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);int t=1;cin>>t;while(t--){solve(); }return 0;
}
这下真不开long long见祖宗了……
这个题就提供了一种新思路,对于位运算相关的题,若每位在计算贡献时相互独立,那么就可以考虑进行拆位,将所有数的同一位累加起来,最后计算总贡献。
具体实现就是用个cnts表存所有数在该位上1的数量,然后只需要遍历一遍枚举ak即可。对于每个ak,就是取ak每一位的数字,若是0,那就只有和1异或得到1才能对累加和产生贡献,所以就从cnts数组里拿个数,再乘以该位的权值。若是1那就找0的个数即可。
六、F. Trulimero Trulicina
#include <bits/stdc++.h>
using namespace std;//注意:要求三是指上下左右的数都不相同
//思路:1.若m%k!=0,则从上往下从左往右顺着1~k填即可
// 横向必满足,纵向两数之间必差m个数,又因为m%k!=0,所以必不同
// 2.若m%k==0,就考虑每行分成m/k个区域,每个区域顺着填,下一行往前移一个填
// e.g.当m=9,k=3 -> 123 123 123
// 231 231 231
// 123 123 123
// ……typedef long long ll;int n,m,k;void solve()
{cin>>n>>m>>k;vector<vector<int>>grid(n,vector<int>(m));if(m%k!=0){for(int i=0;i<n;i++){for(int j=0;j<m;j++){grid[i][j]=(m*i+j)%k+1;}}}else{for(int i=0;i<n;i++){for(int j=0;j<m;j++){grid[i][j]=i%2==0?j%k+1:(j+1)%k+1;}}}for(int i=0;i<n;i++){for(int j=0;j<m;j++){cout<<grid[i][j]<<" ";}cout<<endl;}
}int main()
{ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);int t=1;cin>>t;while(t--){solve(); }return 0;
}
这个构造题的思路太逆天了……
注意到(注意力惊人),若m%k不等于0,那直接从上往下从左往右顺着填1~k即可。这样的话左右肯定不相同,而上下的话观察可得两数中间正好差k个数,又因为m%k不等于0,所以上下必不可能相等。若m%k等于0,那就考虑将列分成m/k个区域,第一行每个区域从1~k顺着填,第二行把1~k往前移一位,按照2,3,4~k,1这个顺序填,第三行再从1~k,之后轮换两种填法即可。(逆天思路)
七、G. Chimpanzini Bananini
#include <bits/stdc++.h>
using namespace std;//纯纯数据结构设计题
//思路:同时维护正序和、逆序和,并用变量记录是否处于reverse状态typedef long long ll;
typedef pair<int,int> pii;void solve()
{int q;cin>>q;deque<int>nums;bool rev=false;//记录是否处于reverse状态ll sum1=0;//正序累加和ll sum2=0;//逆序累加和ll sum=0;//数字累加和ll head1=1;//正序的头下标 -> 初始值不影响最后结果,能修正回来ll head2=1;//逆序的头下标auto put=[&](bool dir,int k){sum+=k;if(!dir)//尾插{//e.g. k=6//操作前: // num: 5 1 2 3 4// idx1: 0 1 2 3 4// idx2: 4 3 2 1 0// idx: 1 2 3 4 5// nums.size()=5,head1=0,head2=0//操作后: // num: 5 1 2 3 4 6// idx1: 0 1 2 3 4 5// idx2: 4 3 2 1 0 -1// idx: 1 2 3 4 5 6// nums.size()=6,head1=0,head2=1,sum1+=(0+5)*6,sum2+=(-1)*6sum1+=(head1+nums.size())*k;//sum2+=(--head2)*k;nums.push_back(k);}else{sum1+=(--head1)*k;sum2+=(head2+nums.size())*k;nums.push_front(k);}};while(q--){int op;cin>>op;if(op==1)///旋转{if(!rev)//正序{int x=nums.back();nums.pop_back();sum-=x;//操作前: // num: 1 2 3 4 5// idx1: 1 2 3 4 5// idx2: 5 4 3 2 1// idx: 1 2 3 4 5// nums.size()=5,head1=1,head2=1//操作后: // num: 1 2 3 4 // idx1: 1 2 3 4// idx2: 5 4 3 2// idx: 1 2 3 4// nums.size()=4,head1=1,head2=2,sum1-=(1+4)*5,sum2-=1*5sum1-=(head1+nums.size())*x;sum2-=head2*x;head2++;//逆序头左移//操作前: // num: 1 2 3 4// idx1: 1 2 3 4// idx2: 5 4 3 2// idx: 1 2 3 4// nums.size()=4,head1=1,head2=2//操作后: // num: 5 1 2 3 4 // idx1: 0 1 2 3 4// idx2: 6 5 4 3 2// idx: 1 2 3 4 5// nums.size()=5,head1=0,head2=2,sum1+=0*5,sum2+=(2+4)*5put(true,x);//头插}else{int x=nums.front();nums.pop_front();sum-=x;//操作前: // num: 1 2 3 4 5// rev: 5 4 3 2 1// idx1: 1 2 3 4 5// idx2: 5 4 3 2 1// idx: 1 2 3 4 5// nums.size()=5,head1=1,head2=1//操作后: // num: 2 3 4 5// rev: 5 4 3 2// idx1: 2 3 4 5// idx2: 4 3 2 1// idx: 1 2 3 4// nums.size()=4,head1=2;head2=1,sum1-=1*1,sum2-=(1+4)*1sum2-=(head2+nums.size())*x;sum1-=head1*x;head1++;//操作前: // num: 2 3 4 5// rev: 5 4 3 2// idx1: 2 3 4 5// idx2: 4 3 2 1// idx: 1 2 3 4// nums.size()=4,head1=2,head2=1//操作后: // num: 2 3 4 5 1// rev: 1 5 4 3 2// idx1: 2 3 4 5 6// idx2: 4 3 2 1 0// idx: 1 2 3 4 5// nums.size()=5,head1=2;head2=0,sum1+=(2+4)*1,sum2+=0*1put(false,x);//尾插}}else if (op==2)//反转{rev=!rev;}else //添加{int k;cin>>k;put(rev,k);}//输出时,因为head会因为put小于,所以要加上1到head的距离修正//e.g.队列数字: 6 1 2 3 4 5// 维护下标: -1 0 1 2 3 4 head1=-1// 实际下标: 1 2 3 4 5 6// sum=1+2+3+4+5+6=21// sum1=-6+0+2+6+12+20=34// ans=6+2+6+12+20+30=76// 所以ans=sum1+sum*(1-(head1))=34+21*2=76if(!rev){cout<<sum1+sum*(1-head1)<<endl;}else{cout<<sum2+sum*(1-head2)<<endl;}}
}int main()
{ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);int t=1;cin>>t;while(t--){solve(); }return 0;
}
这个题感觉已经不能用逆天来形容了……
因为必须实现每个操作的时间复杂度都为常数时间,所以就得考虑设计多个数据结构,用空间换时间。
观察可得,因为其中有反转操作,所以考虑同时维护正序指标sum1和逆序指标sum2,再用一个变量记录当前状态是否为反转,最后输出时直接根据状态输出对应指标即可。
接着就需要考虑将旋转和添加操作转化成只对sum1和sum2两头进行操作,而不对中间值操作,最后输出答案的时候再通过别的方法一步修正对。思路就是去修改正序和逆序的下标,若当前状态是正序,在旋转时就是先让sum1减去下标乘以最后一个数。而在头部插入这个数时,因为要尽可能减小对中间值的影响,所以考虑不修改中间值的下标,而是让头部下标head1-1乘以这个数。与此同时,还需要修改逆序指标sum2,因为此时尾部元素在逆序里为头部,所以就是让sum2减去head2乘以该数,同样因为要减小对中间值的影响,所以考虑让头部下标head2+1。之后在正序头部插入这个数时,就是让sum2加上尾部下标,即head2加个数乘以这个数。逆序状态的思路类似。(要命了)
所以可以看出,还需要用一个双端队列deque来时刻维护正常的顺序,以及方便快速查出个数来通过head求尾部下标。
最后就是修正答案的部分。观察例子可以发现,不管是sum1还是sum2,下标的改变是与ans不一致的根本原因,所以考虑从下标入手进行修正。而进一步观察可以发现,此时每个数和正确值的差值就是下标偏移量乘以自己。所以通过求这个偏移量,即正确下标1到head的距离,再乘以所有数的累加和sum,再加上sum1或sum2就是正确答案。
总结
H题就是拼尽全力无法战胜了,学的还是不够呜呜……