24ICPC昆明站补题
原题链接
题解参考 1 2
J Just another Sorting Problem 博弈
题意:给定一个长度为n的排列,Alice 的目标是将排列排序,Bob的目标是阻止Alice在有限步内
排序。Alice 可以交换任意两个位置的数字,Bob只能交换相邻两个位置的数字。AB每次都必须操作。
- A赢:
- 只有一对逆序,且A先操作
- n=2 无论AB谁先
- n=3 123都不在位置上,B先操作,A赢
void solve()
{int n;cin>>n;string s;cin>>s;vector<int>a(n+1);forr(i,1,n)cin>>a[i];//一开始犯蠢想用单调栈求逆序对数...int cnt=0;forr(i,1,n){cnt+=(a[i]!=i);//因为是排列 所以统计不等于下标的数目}if(cnt==0)return cout<<"Alice"<<endl,void();if(cnt==2&&s[0]=='A')return cout<<"Alice"<<endl,void();//手玩发现的特例if(n==2)return cout<<"Alice"<<endl,void();if(n==3){if(s[0]=='B'&&cnt==3){//3 1 2 2 3 1return cout<<"Alice"<<endl,void();}}cout<<"Bob"<<endl;
}
M Matrix Construction 构造
题意:构造一个n×m的矩阵,使得任意“相邻两个位置的数字的和”都互不相等。
没想出来…
看题解是从小到大填充副对角线
- 一个数的相邻位置数字属于另一条副对角线,所有的和从左上到右下递增,能保证不同对角线上的和两两不同
- 相同对角线上,从右上到左下递增,也满足和两两不同
const int N = 4e3+10, C = 1e6 + 10, mod =998244353, inf = 1e9 + 10;
int a[N][N];
void solve()
{int n,m;cin>>n>>m;cout<<"yes"<<endl;int cnt=1;forr(i,1,n+m-1){int x=max(1ll,i-m+1),y=min(m,i);// cout<<x<<' '<<y<<endl;while (x<=n&&y>=1){a[x][y]=cnt++;x++,y--;}}forr(i,1,n){forr(j,1,m)cout<<a[i][j]<<' ';cout<<endl;}
}
H Horizon Scanning 滑窗 角度
const double pi=acos(-1);
struct pos
{double x,y;
};void solve()
{int n,k;cin>>n>>k;vector<pos>a(n+1);vector<double>rad(2*n+1);int id=0;forr(i,1,n){cin>>a[i].x>>a[i].y;if(a[i].x>0)rad[++id]=atan(a[i].y/a[i].x)+(a[i].y<0)*2*pi;//一四else if(a[i].x==0){if(a[i].y==0)k--;//(0,0)无论何时都有这个点else rad[++id]=a[i].y/abs(a[i].y)*pi/2;}else rad[++id]=pi+atan(a[i].y/a[i].x);//二三}sort(rad.begin()+1,rad.begin()+id+1);forr(i,1,id){rad[id+i]=rad[i]+2*pi;//绕圈}double ans=0;forr(r,k+1,id+k){int l=r-k;ans=max(rad[r]-rad[l],ans);}cout<<fixed<<setprecision(10)<<ans<<endl;
}
G GCD BFS
题意:给定两个正整数a和b,每次可以选一个数并减去gcd(a,b)。问:至少要多少次操作,才能使a和b都变成0?
- (0,b)→(0,0)(0,b)\rightarrow (0,0)(0,b)→(0,0)一步到位
- a较小,先把a变成0,就考虑gcd(a,b)会是什么值
- 考虑二进制,若a&1,b&1都是0,那么gcd(a,b)≥2gcd(a,b)\geq 2gcd(a,b)≥2
- 若有一个是1,那么gcd(a,b)&1=1gcd(a,b)\&1=1gcd(a,b)&1=1,减去gcd(a,b)会把最低位1去掉,下一次继续gcd(a,b)≥2gcd(a,b)\geq 2gcd(a,b)≥2
- 所以每次对a会去掉2,最后(a,b)→(0,b)(a,b)\rightarrow (0,b)(a,b)→(0,b)步数不会太多
struct st{int a,b,stp;
};void solve()
{int a,b;cin>>a>>b;queue<st>q;q.push({a,b,0});while (q.size()){auto [na,nb,stp]=q.front();q.pop();if(na==0||nb==0){cout<<stp+1<<endl;break;}int g=__gcd(na,nb);q.push({na-g,nb,stp+1});q.push({na,nb-g,stp+1});}}
L Last Chance: Threads of Despair 贪心 策略
题意:敌我双方分别有n和m个单位,我方每个单位可以攻击一次,攻击一个敌方单位会使战斗双
方分别-1 生命值,当一个单位生命值清零后会发生一次爆炸,使所有单位生命值-1,询问是否
存在一种攻击方案消灭所有敌方单位。
void solve()
{int n,m;cin>>n>>m;vector<int>a(n+1),b(m+1);forr(i,1,n)cin>>a[i];forr(i,1,m)cin>>b[i];sort(a.begin()+1,a.end());sort(b.begin()+1,b.end());//爆炸伤害>=单次攻击伤害 贪心尽量使用爆炸int atk=0;//攻击次数forr(i,1,n){atk+=a[i]>1;//,所有生命值超过1的我方单位都拥有一次攻击机会}atk+=a[1]==1;//生命值等于1的我方单位加起来总共拥有一次攻击机会int j=1,i=1,bom=0;//从小到大消耗 看能引发多少连锁爆炸 影响后面血量较大的人while (j<=m){while (i<=n||j<=m){//思路:找a[i]中能引发的爆炸会带走多少b中的敌人//先找a[i]中能爆炸的if(i<=n&&a[i]-1<=bom){//更新可爆炸的数量 a[i]-1 因为要用在攻击反伤1滴血i++;bom++;//连锁爆炸数量}else if(j<=m&&b[j]<=bom){//如果当前a[i]凑的连锁爆炸能带走b[j]j++,bom++;//连锁爆炸}else break;//再也没有连锁爆炸}//连锁爆炸推进不下去 用攻击推进爆炸if(j<=m){if(atk>=b[j]-bom){atk-=(b[j]-bom);j++;bom++;}else break;//推进不下去}}if(j>m)ryes;else rno;
}
C Coin 分治 二分 逆推
void solve()
{int n,k;cin>>n>>k;if(k>=n)return cout<<n<<endl,void();auto cal=[&](int x)->int{//计算x长度 多少轮能全部消除int sm=0;while(x){int cnt=(x+k-1)/k;//删去多少个数// int ed=1+(cnt-1)*k;//最后一个数位置s// int d=(x-ed+cnt)/cnt;//删除cnt轮数 ed后面可能有重复删除cnt的操作int ed=(cnt-1)*k;//最后一个数位置sint d=(x-ed+cnt-1)/cnt;//删除cnt轮数 ed后面可能有重复删除cnt的操作x-=d*cnt;sm+=d;//轮数}return sm;};// forr(i,1,n){// cout<<i<<':'<<cal(i)<<' ';// }cout<<endl;/*20 4
1:1 2:2 3:3 4:4 5:4 6:5 7:5 8:6 9:6 10:6 11:7 12:7 13:7 14:7 15:8 16:8 17:8 18:8 19:8 20:9 第一个cal(x-1)<cal(x)的x会留到最后
*/int ncal=cal(n);int ans;if(ncal<=k||ncal-k>=2e6){//极大/极小 一边倒的 可以二分找第一个cal(mid)==ncal//一次操作消除(n+k-1)/k个数 在最后(n+k-1)/k个数中找 差1的int l=n-(n+k-1)/k+1,r=n; while (l<r){int mid=(l+r)>>1;if(cal(mid)==ncal)r=mid;//找第一个 如果相等 r往前推else l=mid+1;//cal(l-1)<ncal}return cout<<l<<endl,void();}//现在倒数k+1轮int pre=k;ncal-=k;//去掉最后一个一个删的过程forr(i,1,ncal){//向更前面的轮找int now=pre+(pre+k-2)/(k-1);pre=now;}ans=pre;cout<<ans<<endl;
}