目录
D.Chocolate
H.Matches
J.Roulette
K.Subdivision
M.Water
思路:当n=1且m=1时候先手必输,然后1*k(k>=2)的情况下后手必输,因为先手可以选到只剩下一个格子。而在其它情况里先手第一步可以先选(1,1)的格子,然后后手无论怎么选,先手都能使得在他选完之后,使剩下来的格子形成不了矩形,直到后手将剩下1*k的矩形,此时先手必胜。
void solve() {
int n,m;
cin>>n>>m;
if(n==1&&m==1) cout<<"Walk Alone"<
思路:我们将a[i]>=b[i]的序对称为序1,a[i]六种情况:
1.序1与序1(等价于序2与序2)不交
可以看出对答案的贡献为2*(C-B)。
2.序1与序1(等价于序2与序2)相交
可以看出对答案的贡献为0。
3.序1与序1(等价于序2与序2)包容
可以看出对答案的贡献为0。
4.序1与序2(等价于序2与序1)不交
可以看出对答案的贡献为2*(D-B)。
5.序1与序2(等价于序2与序1)相交
可以看出对答案的贡献为-2*(B-D)。
6.序1与序2(等价于序2与序1)包容
可以看出对答案的贡献为-2*(B-D)。
综上可得,只有两个序列对类型不同,且他们有交集时,才会对答案产生负贡献,贡献的大小为-2*相交线段长度,所以我们可以将两种序对标记一下存入容器,左端点排序后遍历寻找不同类型序对的相交线段的最大长度,具体实现见代码。
代码:
struct st {
int l,r,id;
};
bool cmp(st a,st b) {
return a.lv;
void solve() {
int n,k,sum=0,ans=0;
//ans储存最长相交线段
maxx[0]=maxx[1]=-inf;
//分别记录两种线段的前缀右端点的最大值
cin>>n;
for(int i=1; i<=n; i++)cin>>a[i];
for(int i=1; i<=n; i++) {
cin>>k;
sum+=abs(a[i]-k);//记录原本的答案
if(k<=a[i])v.push_back({k,a[i],0});//分为两种序对,标记存储
else v.push_back({a[i],k,1});
}
sort(v.begin(),v.end(),cmp);
for(int i=0; iv[i].l) { //如果前缀右端点的最大值比当前的左端点大,则说明产生了交集
if(maxx[!now]
思路:接下来的描述中1代表赢,0代表输。我们先对它们每个1进行分治,可以看出每个1对于答案的贡献一定是1,因为连续的x-1位0对于的答案的贡献为-(2^x-1),而连续的x-1位0后的第x位1的贡献为2^x次,它们的和即为-(2^x-1)+2^x=1。
比如0001,前面三场0的负贡献分别为-1,-2,-4,总共为-7,而最后一场1的贡献为4*2=8,所以总贡献为8-7=1。
因此,Walk Alone赢的次数固定为m次。而对于每个1前面的最多有几个0我们是可以计算的,只要负贡献不大于当前的本钱就行,
每次分治Walk Alone赢的基础概率为1/2,而后面的第x个的0会产生(1/2)^(x+1)的贡献,这表示形成之前x-1情况的概率*1/2,所以每位1的总贡献为1/2+(1/2)^2...(1/2)^(零的个数+1),最后分块求和就完事了。
代码:
int qkp(int a,int b) {
int ans=1;
while(b) {
if(b&1)ans=ans*a%mod;
b>>=1;
a=a*a%mod;
}
return ans;
}
void solve() {
int n,m,l,r,ans=1,base=1;
cin>>n>>m;
for(int i=0; i<=34; i++) {
l=max(n+1,base),r=min(n+m,base*2-1),base*=2;
//l表示2^i,r表示2^(i+1)-1
if(r
思路:根据第二个样例可看出,肯定是把点加到最后层次的边上为最优,因为若在很早就把点给加到边上了,后面本来可以往下走的边就被“堵塞”了,所以肯定是越晚加点越好,所以我们可以跑一遍bfs,若跑到叶子节点了或者和之前跑过的节点“碰头”了,则说明不能再晚加点了,只能现在加点,答案加上k-步数。若还能再跑,则答案+1,表示当前节点对于答案的贡献为1。注意判断步数与k的大小关系。
void solve() {
int n,m,k,ans=1;
cin>>n>>m>>k;
for(int i=1; i<=m; i++) {
int a,b;
cin>>a>>b;
e[a].push_back(b);
e[b].push_back(a);
}
queueq;
q.push({1,0});
while(!q.empty()) {
int u=q.front().first,fa=q.front().second;
q.pop();
for(auto x:e[u]) {
if(x==fa)continue;
if(dep[x]||e[x].size()==1) {//若跑到了根节点或者碰头了,则答案加上k-步数
ans+=max(0ll,k-dep[u]);
continue;
}
dep[x]=dep[u]+1;
if(dep[u]+1<=k)ans++;
q.push({x,u});
}
}
cout<
思路:首先,该题的x可以表示为sA+rB,也就是sA+rB=x。因为+A和+B都可以通过倒满一杯水然后喝掉来获得,而-A可以通过用一直用满杯的B杯倒满A杯,直至B杯中剩余kB-A量的水。-B亦可以通过次方式获得。既然+A,+B,-A,-B都可以获得,那么我们能够得到的水量自然能用sA+rB来表示。
根据裴蜀定理,若无解,则x%gcd(s,r)!=0,反之则一定有解。而这个sA+rB的式子可以总共分为两种情况:
1.s*r>=0,并且s和r不能同时小于等于0,因为x>=0。此时的最小操作数很显然,就是重复倒s杯容量为A的水然后喝下,重复倒r杯容量为B的水然后喝下,总操作数为2*(r+s)。
2.s*r<0,也就是s和r中有一个小于0的情况。不妨设s>r且s>0,r<0。此时表达式可以先转换一下:
sA+rB=>(s+r)A-r(A-B)
(s+r)A可以由s*r>=0时一样的方式来获得,操作数为2*(r+s),而-r(A-B)则可以通过:先倒满A杯->将A杯的水倒入B杯(此时A杯剩A-B的水,B杯满水)->喝掉A-B->倒掉B,总共四步来获得。并且最后那一步的B杯不用倒,因为B杯已经用不到了,省下了一步,总共是4*(-r)-1步。
然后我们将这两部代入公式得到:
(s+r)A-r(A-B)
=>2*(r+s)+4*(-r)-1
=>2*s-2*r-1
=>2*(s-r)-1
因此,此时的操作数至少为2*abs(s-r)+1步。
最后,我们可以先通过exgcd找出一组解,也就是s0*A+r0*B=x中的s0和r0。又因为该式子可以表示为(s0+t*B)A+(r0-t*A)B=x,也就是说解可以表示为:ss=s0+t*B,rr=r0-t*A
=>ss*A+rr*B=x
此时ss或rr趋于0的时候答案最小,计算s0+t*B=0得到t=-s0/b,计算r0-t*A=0得到t=r0/A,枚举t的上下界取min值即可。
代码:
void exgcd(int a,int b,int &x,int &y) {
if(!b)x=1,y=0;
else exgcd(b,a%b,y,x),y-=a/b*x;
}
void f(int t) {
int ss=s+b*t,rr=r-a*t;
if(rr>=0&&ss>=0)ans=min(ans,2*(rr+ss));
else ans=min(ans,2*abs(rr-ss)-1);
}
void solve() {
ans=inf;
cin>>a>>b>>x;
int g=__gcd(a,b);
if(x%g) {
cout<<-1<