2024 ICPC National Invitational Collegiate Programming Contest, Wuhan Site
tot:赛时就出了三题,罚时343。然后补了两题F,M,再然后过了好一段时间,再补了两题D,E。共计七题。只能说收获不少。
Problem - I - Codeforce--CyclicAppleStringss
思路:队友写的签到题。
//队友写的,签到题
//https://codeforces.com/gym/105143/problem/I
void solve(){
string s; cin >> s;
int p = 0, m = 0;
int sz = s.size();
for (int i = sz - 1 ; i >= 0; i --) {
if (s[i] == '0') {
p = i;
break;
}
}
for (int i = 0 ; i < sz; i ++) {
if (s[i] == '1') {
m = i;
break;
}
}
if (is_sorted(s.begin(), s.end())) {
cout << 0 << '\n';
return ;
}
// cout << p << '\n'
int ans = 0;
for (int i = m; i <= p; i ++) {
while (i < p && s[i + 1] == '1') i ++;
while (i < p && s[i + 1] == '0') i ++;
// i --;
// cout << i << '\n';
ans ++;
}
cout << ans << '\n';
}
Problem - K - Codeforces
思路:队友写的,打表找规律。
const int N=2e6+10,M=1e4+10;
const int INF=0x3f3f3f3f;
const int mod=1e9+7;
int n;
int a[N];
//队友写的,打表找到规律
//https://codeforces.com/gym/105143/problem/K
void solve(){
cin >> n;
if(n%4==3){cout << "Pinkie Pie\n"; return ;}
else if(n%4==2){cout << "Pinkie Pie\n"; return ;}
else if(n%4==1){cout << "Fluttershy\n"; return ;}
else cout << "Fluttershy\n";
}
Problem - B - Codeforces--CountlessMe
思路:
从高位往低位贪心. 很早就发现了n次操作是可以变为任意序列的。 但是一直想歪了,一直在正解的旁边乱搞,各种抽象实现..最后才回到正解.
int n;
int a[200005];
从高位往低位贪心.
很早就发现了n次操作是可以变为任意序列的。
但是一直想歪了,一直在正解的旁边乱搞,各种抽象实现..最后才回到正解.
//https://codeforces.com/gym/105143/problem/B
void solve(){ B
cin >> n;
int sum=0;
for(int i=1;i<=n;i++){
cin >> a[i];
sum+=a[i];
}
int ans=0;
for(int i=31;~i;i--){
int one=1ll<<i;
if(one>sum) continue;
int ma=((1ll<<i)-1)*n;
if(ma<sum){
ans|=1<<i;
sum-=min(n,sum/one)*one;
}
}
cout << ans;
}
Problem - F - Codeforces--CustomMadeClothes
思路:已知矩阵a[i][j]>=a[i-1][j] && a[i][j]>=a[i][[j-1]. 很遇到多题了已经,求第k大都和二分相关!
交互题--求一个未知n*n矩阵的第k大 二分答案+技巧的询问--跟暑假写的一题乘法表的二分方法一模一样的! 二分答案然后从左下角到右上角询问,最坏只会问2*n*logn(n*n)=39863<=50000次.
int n,k;
bool check(int cc){
int x=n,y=1,cnt=0; //从左下角到右上角
while(x>=1&&y<=n){
cout<<"? "<<x<<" "<<y<<" "<<cc<<endl; //记得注释掉def endl
int ret; cin>>ret;
if(ret) cnt+=x,y++; //maze[x][y]<=cc
else x--;
}
return cnt>=k; //为什么这里改成cnt<=k,然后下面二分也跟着换位置会wa2?...按理来说是同理的.不懂
}
交互题--求一个未知n*n矩阵的第k大
二分答案+技巧的询问--跟暑假写的一题乘法表的二分方法一模一样的!
//https://codeforces.com/gym/105143/problem/F
void solve(){ F
cin>>n>>k;
k=n*n-k+1; 求第k大,即求第n*n-k+1小;这样转化了,才能利用小于等于中的等于号,否则直接求有多少个大于k的话,取不到等号
int l=1,r=n*n,ans=0;
while(l<=r){
int mid=(l+r)>>1;
if(check(mid)){
ans=mid;
r=mid-1;
}
else l=mid+1;
}
cout<<"! "<<ans;
}
Problem - M - Codeforces--Merge
思路:
从最大值开始贪心: ①如果最大值x为偶数,那么可以优先造出x+1,得到x+x+1. 如果不能造出x+1,那么再考虑造出x-1,得到x+x-1. 如果都不行,那么考虑次大的数字.. ②如果最大值y为奇数,那么y+1和y-1都是偶数,偶数是不可能造出来的(相邻的数相加为奇数),那么直接查询y+1,y-1是否存在即可. 但是会有多次的造数字(例如得到x+x+1之后,还要接着考虑是否存在(x+x+1)+1 or (x+x+1)-1),如何实现? 递归. hack: 4 -- 1 2 4 8 8 ->7,9 7 <-3+4 , 3<-1+2 这个样例可以体现key1,key2. 如果一直递归下去,太暴力了? --完全可以,复杂度是o(n*log1e18) 1.2e7 hack,出现数字多用的情况: 最终找到的原因是--getNum考虑到了先cnt--,不行再回溯;但是dfs没有考虑到,dfs也应该先cnt[num]--的,因为无论如何,这个num都要cnt--的,也不用回溯 10 5 1 2 3 6 4 9 8 5 1--good:17 13 5 5 3 1 5 4 3 13 6 3--good:13 13 3 总的来说,还是dfs不熟练,对于递归没有那么精湛,细节注意不到位.
int n;
unordered_map<int,int> cnt;
vector<int> vct,ans;
bool getNum(int need){ //o(log1e18) 此处need只会是奇数
if(cnt[need]>=1){
cnt[need]--;
return true;
}
int less=need/2,big=need/2+1;
if(less%2==0&&cnt[less]>=1){ key:偶数的满足再考虑奇数的
cnt[less]--; //key1:先减!
if(getNum(big)) return true;
else{
cnt[less]++; //key2:如果不行就回溯
return false;
}
}
else if(big%2==0&&cnt[big]>=1){
cnt[big]--; //key1:先减!
if(getNum(less)) return true;
else{
cnt[big]++; //key2:如果不行就回溯
return false;
}
}
return false;
}
void dfs(int num){ o(n)
if(num%2==0){ //num为偶数
//key:这里要给num+num(+-)1的cnt加上
cnt[num]--; key中key:这里要先扣掉,不然剩下数字2,1,1时,2会被使用两次,导致造出一个5.
debug很久,是这个地方。。上门getNum那里已经想到了要先扣掉,这里又遗漏了这个细节..
if(getNum(num+1)) cnt[num+num+1]++,dfs(num+num+1);
else if(getNum(num-1)) cnt[num+num-1]++,dfs(num+num-1);
else ans.emplace_back(num);
}
else{ //num为奇数
//key:这里要给num+num(+-)1的cnt加上
if(cnt[num+1]>=1) cnt[num+1]--,cnt[num]--,cnt[num+num+1]++,dfs(num+num+1);
else if(cnt[num-1]>=1) cnt[num-1]--,cnt[num]--,cnt[num+num-1]++,dfs(num+num-1);
else cnt[num]--,ans.emplace_back(num);
}
}
从最大值开始贪心:
①如果最大值x为偶数,那么可以优先造出x+1,得到x+x+1.
如果不能造出x+1,那么再考虑造出x-1,得到x+x-1.
如果都不行,那么考虑次大的数字..
②如果最大值y为奇数,那么y+1和y-1都是偶数,偶数是不可能造出来的(相邻的数相加为奇数),那么直接查询y+1,y-1是否存在即可.
但是会有多次的造数字(例如得到x+x+1之后,还要接着考虑是否存在(x+x+1)+1 or (x+x+1)-1),如何实现? 递归.
//hack:
//4 -- 1 2 4 8
//8 ->7,9
//7 <-3+4 , 3<-1+2
//这个样例可以体现key1,key2.
//如果一直递归下去,太暴力了? --完全可以,复杂度是o(n*log1e18) 1.2e7
//hack,出现数字多用的情况:
最终找到的原因是--getNum考虑到了先cnt--,不行再回溯;但是dfs没有考虑到,dfs也应该先cnt[num]--的,因为无论如何,这个num都要cnt--的,也不用回溯
//10 5 1 2 3 6 4 9 8 5 1--good:17 13 5 5 3 1
//5 4 3 13 6 3--good:13 13 3
总的来说,还是dfs不熟练,对于递归没有那么精湛,细节注意不到位.
//https://codeforces.com/gym/105143/problem/M
void solve(){ M-merge
cin>>n;
for(int i=1;i<=n;i++){
int x; cin>>x;
cnt[x]++,vct.emplace_back(x);
}
sort(vct.begin(),vct.end(),greater<int>());
for(auto v:vct){
if(cnt[v]<=0) continue;
dfs(v);
}
cout<<ans.size()<<endl;
for(auto a:ans) cout<<a<<" ";
}
Problem - E - Codeforces--Boomerang
思路:
一直加叶子节点,动态维护树的直径,不能按照root求出初始的直径,然后就认为这样是最长的了.实际上直径的长度有可能会改变,并且不经过root. debug了半天才发现错例,发现直径是会变的,并且可能不经过root.一直都认为直径是经过root的那条..再改吧. key:众所周知的结论是距离树上某节点最远的点一定为当前直径的端点, 即加入x后新的直径仅可能是(u,v),(u,x),(x,v)三者之一,套路地求lca即可得到路径长度。 那么可以求出各个时刻的直径长度,对于每个k可以进行二分,O(1)查询此时刻的直径长度.
int n;
vector<int> vct[200005];
int root,t0;
int dep[200005];
int fa[200005][20];
vector<bool> vis(200005,false);
void dfs(int u,int father){ //初始化出dep和fa
dep[u]=dep[father]+1;
fa[u][0]=father;
for(int i=1;i<20;i++) fa[u][i]=fa[fa[u][i-1]][i-1];
for(auto v:vct[u]) if(v!=father) dfs(v,u);
}
inline int lca(int u,int v){
if(dep[v]>dep[u]) swap(u,v); //让深的往上跳
for(int i=19;i>=0;i--) if(dep[fa[u][i]]>=dep[v]) u=fa[u][i];
if(u==v) return u;
for(int i=19;i>=0;i--) if(fa[u][i]!=fa[v][i]) u=fa[u][i],v=fa[v][i];
return fa[u][0];
}
int dist(int u,int v){ return dep[u]+dep[v]-2*dep[lca(u,v)]; } //求树中任意两点距离.
vector<int> len;
inline bool check(int x,int k){
int len0=0;
if(x>=(int)len.size()-1) len0=len[(int)len.size()-1];
else len0=len[x];
return len0/2<=(x-t0)*k;
}
//一直加叶子节点,动态维护树的直径,不能按照root求出初始的直径,然后就认为这样是最长的了.实际上直径的长度有可能会改变,并且不经过root.
//debug了半天才发现错例,发现直径是会变的,并且可能不经过root.一直都认为直径是经过root的那条..再改吧.
key:众所周知的结论是距离树上某节点最远的点一定为当前直径的端点,
即加入x后新的直径仅可能是(u,v),(u,x),(x,v)三者之一,套路地求lca即可得到路径长度。
那么可以求出各个时刻的直径长度,对于每个k可以进行二分,O(1)查询此时刻的直径长度.
//Boomerang
//https://codeforces.com/gym/105143/problem/E
void solve(){ //E
cin>>n;
for(int i=1;i<=n-1;i++){
int u,v; cin>>u>>v;
vct[u].emplace_back(v);
vct[v].emplace_back(u);
}
cin>>root>>t0;
if(n==1){ cout<<t0<<endl; return; } //特判!!
dfs(root,0);
int ept1=root,ept2=root; //endPoint
vis[root]=true;
queue<int> que;
que.emplace(root);
int d=2; //d从2开始
len.emplace_back(0);
while(!que.empty()){ //层序遍历
if(dep[que.front()]>d) {
len.emplace_back(dist(ept1,ept2)+1);
d++;
}
int from=que.front();
que.pop();
if(dist(from,ept1)>dist(ept1,ept2)) ept2=from;
else if(dist(from,ept2)>dist(ept1,ept2)) ept1=from;
for(auto v:vct[from]) if(!vis[v]) que.emplace(v),vis[v]=true;
}
len.emplace_back(dist(ept1,ept2)+1);
// for(int i=1;i<len.size();i++) cout<<len[i]<<" ";
for(int k=1;k<=n;k++){
int l=t0+1,r=1e8,res=t0;
while(l<=r){
int mid=(l+r)>>1;
if(check(mid,k)){
res=mid;
r=mid-1;
}
else l=mid+1;
}
cout<<res<<" ";
}
}
//31
//1 2
//2 3
//3 4
//4 5
//5 6
//6 7
//7 20
//3 8
//8 9
//8 21
//21 22
//22 23
//4 24
//24 25
//25 28
//25 29
//4 26
//26 27
//27 30
//27 31
//5 15
//15 16
//16 17
//15 18
//18 19
//5 10
//10 13
//13 14
//10 11
//11 12
//25 3
//expect:8 5 4... output:7 5 4...
Problem - D - Codeforces--ICPC
思路:
说是简单二维dp,但是看了很久很久(dp还是太差劲),终于懂多了.. 首先很明显的是,要么不拐弯,要么只有一次拐弯. 那么可以预处理出数组g[s][t]:起始位置为s,不拐弯,走t步可得最大的价值. 再从左往右,和右往左更新,枚举拐弯一次的情况--key
最关键的是:这个dp过程,怎么取到先左走三(多)个,再往右一直走的情况? dp[s][t]=max(dp[s-1][t-1],g[s][t]); 这里的dp[s-1][t-1]是有传递性的,不只是说往左走一步然后往右一直走; dp[s-1][t-1]这个状态可能是由dp[s-2][t-2]转移过来的.. 以此类推,那么无论是先往左/右走多少步,再往另一个方向一直走都会覆盖到,所以是不会漏状态的... 好一个传递性....多写多思考多收获..
int n;
int pre[5003];
//1024mb..5e7+5e7能开.
int g[5003][2*5003]; //g[s][t]定义为:起始点为s,可走t步,不拐弯,可得的最大代价.
int dp[5003][2*5003]; //dp[s][t]定义为:起始点为s,可走t不,可拐弯,可得的最大代价.
//参考题解:
//https://blog.csdn.net/Lazy_ChessPlayer/article/details/138479914 --这个写的好很多,通俗易懂.
//https://zhuanlan.zhihu.com/p/696036772
//说是简单二维dp,但是看了很久很久(dp还是太差劲),终于懂多了..
//首先很明显的是,要么不拐弯,要么只有一次拐弯.
//那么可以预处理出数组g[s][t]:起始位置为s,不拐弯,走t步可得最大的价值.
//再从左往右,和右往左更新,枚举拐弯一次的情况--key
最关键的是:这个dp过程,怎么取到先左走三(多)个,再往右一直走的情况?
dp[s][t]=max(dp[s-1][t-1],g[s][t]); 这里的dp[s-1][t-1]是有传递性的,不只是说往左走一步然后往右一直走;
dp[s-1][t-1]这个状态可能是由dp[s-2][t-2]转移过来的..
以此类推,那么无论是先往左/右走多少步,再往另一个方向一直走都会覆盖到,所以是不会漏状态的...
好一个传递性....多写多思考多收获..
//https://codeforces.com/gym/105143/problem/D
void solve(){ //D
cin>>n;
for(int i=1;i<=n;i++) cin>>pre[i],pre[i]+=pre[i-1];
//通过前缀和预处理出不拐弯情况下的可得最大价值.
for(int s=1;s<=n;s++){
for(int t=0;t<=2*n;t++){
int l=max(0ll,s-t);
int r=min(n,s+t);
g[s][t]=max(pre[s]-pre[l-1],pre[r]-pre[s-1]);
}
}
//往右走,向左拐弯.
for(int s=1;s<=n;s++){
for(int t=1;t<=2*n;t++){
//往左拐 or 直走
dp[s][t]=max(dp[s-1][t-1],g[s][t]);
}
}
//往左走,向右拐弯.
for(int s=n;s>=1;s--){
for(int t=2*n;t>=1;t--){
//往右拐 or 直走
dp[s][t]=max(dp[s][t],dp[s+1][t-1]); //往右拐不一定比当前更优
dp[s][t]=max(dp[s][t],g[s][t]);
}
}
int ans=0;
for(int i=1;i<=n;i++){
int res=0;
for(int j=1;j<=2*n;j++) {
res^=(j*dp[i][j]);
// cout<<dp[i][j]<<" ";
}
// cout<<endl;
ans^=(i+res);
}
cout<<ans;
cout<<2*1000*log2(1e6);
}