目录
D.Chocolate
H.Matches
J.Roulette
K.Subdivision
D.Chocolate
思路:当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"<<endl;else cout<<"Kelin"<<endl;
}
H.Matches
思路:我们将a[i]>=b[i]的序对称为序1,a[i]<b[i]的序对称为序2,所有的序列两两配对总共可以分为六种情况:
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.l<b.l;//根据左端点来从小到大排序
}
vector<st>v;
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; i<v.size(); i++) {int now=v[i].id;if(maxx[!now]>v[i].l) { //如果前缀右端点的最大值比当前的左端点大,则说明产生了交集if(maxx[!now]<v[i].r)ans=max(ans,maxx[!now]-v[i].l);//若小于当前右端点,交集长度则为前缀右端点的最大值-当前左端点else ans=max(ans,v[i].r-v[i].l);//否则,则为当前的线段长度(相当于当前线段整个都被包含)}maxx[now]=max(maxx[now],v[i].r);//更新前缀右端点的最大值}cout<<sum-2*ans<<endl;//答案减去最大的负贡献
}
J.Roulette
思路:接下来的描述中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<l)continue;int sum=(qkp(2,i)-1)*qkp(qkp(2,i),mod-2)%mod;//(qkp(2,i)-1)*qkp(qkp(2,i),mod-2)表示的是(2^i-1)/2^i,为等比数列求和公式 ans=ans*qkp(sum,r-l+1)%mod;//答案为累乘的结果 }cout<<ans<<endl;
}
K.Subdivision
思路:根据第二个样例可看出,肯定是把点加到最后层次的边上为最优,因为若在很早就把点给加到边上了,后面本来可以往下走的边就被“堵塞”了,所以肯定是越晚加点越好,所以我们可以跑一遍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);}queue<PII>q;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<<ans<<endl;
}