容易发现一个结论:每隔 k k k 位的字符应该是相同的。于是就可以确定这个字符串的一部分。
剩下的部分只需要保证 字符串前 k k k 个字符中 01 01 01 数量相同即可,剩下的 ?
也就确定了,代码如下:
#include
#include
#include
#include
using namespace std;
#define maxn 300010
int T,n,k;
char s[maxn];
int main()
{
scanf("%d",&T);while(T--)
{
scanf("%d %d",&n,&k);
scanf("%s",s+1);
int ans=1,tot=0;
for(int i=1;i<=k;i++){
int p=-1;
for(int j=i;j<=n;j+=k){
if(s[j]!='?'){
if(p!=-1&&p!=s[j]-'0')ans=0;
else p=s[j]-'0';
}
}
if(p!=-1){
for(int j=i;j<=n;j+=k)
if(s[j]=='?')s[j]=p+'0';
}
}
if(ans){
int zero=0,one=0;
for(int i=1;i<=k;i++)if(s[i]=='0')zero++;else one++;
for(int i=1;i<=k;i++)if(s[i]=='?'){
if(zero<k/2)s[i]='0',zero++;
else s[i]='1',one++;
}
zero=0,one=0;
for(int i=1;i<=k;i++)if(s[i]=='0')zero++;else one++;
if(zero!=one)ans=0;
for(int i=k+1;i<=n;i++){
if(s[i]=='?')s[i]=s[i-k];
if(s[i]=='0')zero++;else one++;
if(s[i-k]=='0')zero--;else one--;
if(zero!=one)ans=0;
}
}
if(ans)printf("YES\n");else printf("NO\n");
}
}
想象一下一开始两人会怎么走:Alice肯定往Bob那里走,Bob肯定会远离Alice,最后Bob会被赶到叶子节点,然后Alice与他的距离一定是 d a da da,因为此时Alice就可以封锁Bob尽可能多的逃跑路线。
此时,Bob只有满足 d b > 2 × d a db>2\times da db>2×da 才能逃出去,所以这就是判输赢的条件。
但是还需要满足树上存在一条长度大于 2 × d a 2\times da 2×da 的链,不然Bob没地方跑,这个判一下直径长度即可。
代码如下:
#include
#include
#include
#include
using namespace std;
#define maxn 100010
#define pb push_back
int T,n,a,b,da,db;
vector<int>e[maxn];
int dis[maxn],fur;
void dfs(int x,int fa){
if(dis[x]>dis[fur])fur=x;
for(int y:e[x])if(y!=fa){
dis[y]=dis[x]+1;
dfs(y,x);
}
}
int main()
{
scanf("%d",&T);while(T--)
{
scanf("%d %d %d %d %d",&n,&a,&b,&da,&db);
for(int i=1;i<=n;i++)e[i].clear();
for(int i=1,x,y;i<n;i++)scanf("%d %d",&x,&y),e[x].pb(y),e[y].pb(x);
dis[a]=0;fur=a;dfs(a,0);
if(dis[b]<=da){
printf("Alice\n");continue;}
dis[fur]=0;dfs(fur,0);
if(db>da*2&&dis[fur]>da*2)printf("Bob\n");
else printf("Alice\n");
}
}
一个显然且有用的转化:先令每个位置与下标做差,即令 b i = i − a i b_i=i-a_i bi=i−ai。
那么拿了一个 0 0 0 之后,后面的 1 1 1 就能拿了,拿了一个 i i i 之后,后面的 i + 1 i+1 i+1 就都能拿了。
先考虑如何能拿最多:肯定是每次拿最右边的且能拿的位置。转化一下,一个位置 i i i 能拿当且仅当它的左边有至少 b i b_i bi 个能拿(赛时这一步想错了一点然后就没了,太菜了QAQ)。
对于每个 i i i,求出最大的 c i c_i ci 表示左边界至少到 c i c_i ci 位置, i i i 才能拿。然后将询问排序用树状数组维护一下 c c c 就可以了。
代码如下:
#include
#include
#include
using namespace std;
#define maxn 300010
int n,m,a[maxn];
struct TREE{
int tr[maxn];
void add(int x){
for(;x<=n;x+=(x&-x))tr[x]++;}
int sum(int x){
int re=0;for(;x;x-=(x&-x))re+=tr[x];return re;}
}tr;
struct par{
int x,pos;};
vector<par>q[maxn];
int ans[maxn];
int main()
{
scanf("%d %d",&n,&m);
for(int i=1;i<=n;i++)scanf("%d",&a[i]),a[i]=i-a[i];
for(int i=1,x,y;i<=m;i++)scanf("%d %d",&x,&y),q[n-y].push_back((par){
1+x,i});
for(int i=1;i<=n;i++){
if(a[i]>=0){
int l=1,r=i,p=-1;
while(l<=r){
int mid=l+r>>1;
if(tr.sum(i)-tr.sum(mid-1)>=a[i])p=mid,l=mid+1;
else r=mid-1;
}
if(p!=-1)tr.add(p);
}
for(par j:q[i]){
ans[j.pos]=tr.sum(i)-tr.sum(j.x-1);
}
}
for(int i=1;i<=m;i++)printf("%d\n",ans[i]);
}
打表发现,奇数时 Second \text{Second} Second 必胜,偶数时 First \text{First} First 必胜。
先说偶数的构造方法:令 i i i 与 i + n i+n i+n 一组。先假定 Second \text{Second} Second 取每组中较小的,那么取完之后的和模 n n n 一定是 n 2 \dfrac n 2 2n,对于任意一组,假如不取小的取大的,那么总和的变化量都是 n n n,即总和模 n n n 依然是 n 2 \dfrac n 2 2n,也就是说,无论怎么取,模 n n n 都是 n 2 \dfrac n 2 2n,那么模 2 n 2n 2n 就不可能为 0 0 0, Second \text{Second} Second 必败。
然后是奇数时的选取策略:此时 1 1 1 ~ 2 n 2n 2n 的和模 2 n 2n 2n 余 n n n,也就是说,如果能找到一组选取方案模 n n n 为 0 0 0,那么就一定有解。因为找到的这组选取方案模 2 n 2n 2n 要么为 0 0 0 要么为 n n n,假如是 0 0 0 那它就是解,假如是 n n n 那么每一组换成另一个,这一个方案模 2 n 2n 2n 一定为 0 0 0。
考虑如何找到一组选取方案模 n n n 为 0 0 0,此时需要满足 n n n 的剩余系中每个数都被拿了一个,即 0 + 1 + 2 + . . . + n − 1 ≡ 0 ( m o d n ) 0+1+2+...+n-1\equiv 0\pmod n 0+1+2+...+n−1≡0(modn)。
模 n n n 为 i i i 的只有 i i i 和 i + n i+n i+n 两个,两者必拿其中一个,而每一组内也是两者必拿其中一个,考虑建一张图,同组数之间连红边, i i i 与 i + 1 i+1 i+1 连蓝边,像这样(从官方题解那偷来的qwq):
然后就会形成很多个环,每个点恰好连接一条红边一条蓝边,那么每个环一定都是偶环,然后就可以黑白染色,白点集和黑点集分别对应一个方案。
代码如下:
#include
#include
#include
using namespace std;
#define maxn 1000010
#define pb push_back
int n,p[maxn],last[maxn];
vector<int>e[maxn];
bool v[maxn];
vector<int>c[2];int C[2];
void dfs(int x,int col){
v[x]=true;
c[col].pb(x);C[col]=(C[col]+x)%(2*n);
for(int y:e[x])if(!v[y])dfs(y,col^1);
}
int main()
{
scanf("%d",&n);
if(n%2==0){
printf("First\n");fflush(stdout);
for(int i=1;i<=2*n;i++)printf("%d ",i<=n?i:i-n);fflush(stdout);
}else{
printf("Second\n");fflush(stdout);
for(int i=1;i<=2*n;i++){
scanf("%d",&p[i]);
if(last[p[i]])e[i].pb(last[p[i]]),e[last[p[i]]].pb(i);
else last[p[i]]=i;
if(i<=n)e[i].pb(i+n),e[i+n].pb(i);
}
for(int i=1;i<=2*n;i++)if(!v[i])dfs(i,0);
for(int i=0;i<2;i++)if(!C[i]){
for(int j:c[i])printf("%d ",j);
}
fflush(stdout);
}
}
将放砖块转化为删边,假如两个黑块之间的公共边被删了,说明两者在同一块。
由于每删一条边砖块数 − 1 -1 −1,那么问题就变成了最多能删多少条边,由于没有 L L L 形的砖,所以需要保证每一个黑块处都不能删出 L L L 形,这个问题可以转化一下:将边化点,让两个冲突的边之间连边,问题就变成了求最大独立集,由于这是二分图,用网络流解决即可。
代码如下:
#include
#include
#include
using namespace std;
#define maxn 100010
int n,m,S,T,ans=0;
char s[210][210];
struct edge{
int y,z,next;}e[maxn*6<<1];
int first[maxn],len=1;
void buildroad(int x,int y,int z){
e[++len]=(edge){
y,z,first[x]};first[x]=len;}
void ins(int x,int y,int z=1){
buildroad(x,y,z);buildroad(y,x,0);/*printf("ins : %d %d\n",x,y);*/}
int h[maxn],q[maxn],st,ed,cur[maxn];
bool bfs(){
memset(h,0,(T+1)<<2);
h[q[st=ed=1]=S]=1;
while(st<=ed){
int x=q[st++];cur[x]=first[x];
for(int i=first[x];i;i=e[i].next)
if(!h[e[i].y]&&e[i].z)h[q[++ed]=e[i].y]=h[x]+1;
}
return h[T];
}
int dfs(int x,int flow){
if(x==T)return flow; int tt=0;
for(int i=cur[x];i;i=e[i].next){
int y=e[i].y;cur[x]=i;
if(h[y]==h[x]+1&&e[i].z){
int p=dfs(y,min(flow-tt,e[i].z));tt+=p;
e[i].z-=p;e[i^1].z+=p;
}
if(tt==flow)break;
}
if(!tt)h[x]=0;
return tt;
}
int main()
{
scanf("%d %d",&n,&m);S=n*(m-1)+m*(n-1)+1;T=S+1;
for(int i=1;i<=n;i++)scanf("%s",s[i]+1);
// printf("S : %d T : %d\n",S,T);
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++)if(s[i][j]=='#'){
int L=j>1&&s[i][j-1]=='#'?(i-1)*(m-1)+j-1:0;
int R=j<m&&s[i][j+1]=='#'?(i-1)*(m-1)+j:0;
int U=i>1&&s[i-1][j]=='#'?(i-2)*m+j+n*(m-1):0;
int D=i<n&&s[i+1][j]=='#'?(i-1)*m+j+n*(m-1):0;
if(L&&U)ins(L,U);
if(L&&D)ins(L,D);
if(R&&U)ins(R,U);
if(R&&D)ins(R,D);
}
}
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++)if(s[i][j]=='#'){
ans++;
if(j<m&&s[i][j+1]=='#')ins(S,(i-1)*(m-1)+j),ans--;
if(i<n&&s[i+1][j]=='#')ins((i-1)*m+j+n*(m-1),T),ans--;
}
}
while(bfs())ans+=dfs(S,n*m);
printf("%d",ans);
}