正题
A. Balanced Bitstring
这样来考虑:当前区间k合法,那么要往右移一位依然合法,那么必须要保证加进来的位置=弹出去的位置,也就是说对于i<=n-k,要保证,我们判断一下输入的式子是否满足这样的性质就可以了,如果一个相等集合内只有问号,那么我们就待定它,最后看一下01个数是否都没有超过k/2即可.
#include<bits/stdc++.h>
using namespace std;
const int N=300010;
char s[N];
int T,n,k;
int main(){
scanf("%d",&T);
while(T--){
scanf("%d %d",&n,&k);
scanf("%s",s+1);
bool tf=true;
for(int i=1;i<=k;i++) {
int op=0;
for(int j=i;j<=n;j+=k)
if(s[j]=='1') op|=1;
else if(s[j]=='0') op|=2;
if(op==3) {tf=false;break;};
if(!op) continue;
if(op==1) s[i]='1';
else s[i]='0';
}
if(!tf) printf("NO\n");
else{
int t1=0,t2=0;
for(int i=1;i<=k;i++)
if(s[i]=='1') t1++;
else if(s[i]=='0') t2++;
printf(t1<=k/2 && t2<=k/2?"YES\n":"NO\n");
}
}
}
B. Tree Tag
比赛的时候想的并不是很清楚,以Alice为根建树,首先如果da*2>=db,那么肯定是Alice赢,因为它可以走到一个刚好距离Bob db的位置,然后Bob往外跳一定是输.考虑da*2<db,如果第一步Alice就可以抓到Bob,输出Alice,否则我们看直径是否>2*da,如果没有这样的直径,那么输出Alice,因为只要Alice站在直径的中心点,下一步肯定可以抓到Bob.如果有这样的直径,而且Alice第一步抓不到Bob,那么我们先让Bob往子树里最深的点跳,知道跳不动,如果Alice下一步可以抓到Bob,那么Bob就往外跳,可以分类讨论Bob初始点是否为直径端点,来证明Bob一定可以跳到一个Alice抓不到的地方,然后让bob反复横跳就可以了.
#include<bits/stdc++.h>
using namespace std;
const int N=100010;
struct edge{
int y,nex;
}s[N<<1];
int first[N],len=0;
int T,n,a,b,da,db,op;
int mx,p,q;
void ins(int x,int y){
s[++len]=(edge){y,first[x]};first[x]=len;
}
void dfs(int x,int fa,int dep){
if(dep>mx) mx=dep,p=x;
if(x==b) op=dep;
for(int i=first[x];i!=0;i=s[i].nex) if(s[i].y!=fa) dfs(s[i].y,x,dep+1);
}
void ga(int x,int fa,int dep){
if(dep>mx) mx=dep,q=x;
for(int i=first[x];i!=0;i=s[i].nex) if(s[i].y!=fa) ga(s[i].y,x,dep+1);
}
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++) first[i]=0;len=0;
int x,y;mx=0;
for(int i=1;i<n;i++) scanf("%d %d",&x,&y),ins(x,y),ins(y,x);
if(2*da>=db){printf("Alice\n");continue;}
dfs(a,0,0);
if(op<=da){printf("Alice\n");continue;}
mx=0;ga(p,0,0);
printf(mx>2*da?"Bob\n":"Alice\n");
}
}
C. Fixed Point Removal
考虑一次怎么做,d[i]表示[1,i]被删除的个数,如果i<a[i],那么这个位置必没有贡献,否则
.可以发现,考虑y=0的情况,我们发现对于i,可以使i产生贡献的是一个前缀,那么我们要找到这个前缀最右是多少,定义ans[i]为当x<=ans[i]时i可以产生贡献,令b[i]=i-a[i],那么当b[i]=0时,有ans[i]=i,否则相当于在前面的ans[i]中二分一个mid,如果ans[i]>=mid的个数>=b[i],那么mid就是可行的ans,将二分放到线段树上即可.
发现询问只要满足x<=i<=y,x<=ans[i]<=y,显然用主席树维护即可.
#include<bits/stdc++.h>
using namespace std;
const int N=300010;
int n,q;
int a[N],rt[N],ans[N];
vector<int> V[N];
int t[N<<2],tot[6000010],ls[6000010],rs[6000010],T;
int ga(int now,int x,int l=1,int r=n){
if(l==r) return x<=t[now]?l:0;
int mid=(l+r)/2;
if(x<=t[now<<1|1]) return ga(now<<1|1,x,mid+1,r);
else return ga(now<<1,x-t[now<<1|1],l,mid);
}
void ins(int now,int x,int l=1,int r=n){
t[now]++;
if(l==r) return ;
int mid=(l+r)/2;
if(x<=mid) ins(now<<1,x,l,mid);
else ins(now<<1|1,x,mid+1,r);
}
void insert(int&now,int las,int x,int l=1,int r=n){
if(now==0) now=++T;
tot[now]=tot[las]+1;
if(l==r) {tot[now]=tot[las]+1;return ;}
int mid=(l+r)/2;
if(x<=mid) rs[now]=rs[las],insert(ls[now],ls[las],x,l,mid);
else ls[now]=ls[las],insert(rs[now],rs[las],x,mid+1,r);
}
int get_sum(int now,int x,int y,int l=1,int r=n){
if(now==0) return 0;
if(x==l && y==r) return tot[now];
int mid=(l+r)/2;
if(y<=mid) return get_sum(ls[now],x,y,l,mid);
else if(mid<x) return get_sum(rs[now],x,y,mid+1,r);
else return get_sum(ls[now],x,mid,l,mid)+get_sum(rs[now],mid+1,y,mid+1,r);
}
int main(){
scanf("%d %d",&n,&q);
int x,y;
for(int i=1;i<=n;i++) {
scanf("%d",&x);
if(i-x<0) ans[i]=1e9;
else {
ans[i]=min(ga(1,i-x),i);
if(ans[i]) ins(1,ans[i]);
else ans[i]=1e9;
}
}
for(int i=1;i<=n;i++) if(ans[i]!=1e9) insert(rt[i],rt[i-1],ans[i]);
else rt[i]=rt[i-1];
while(q--){
scanf("%d %d",&x,&y);y=n-y;
printf("%d\n",get_sum(rt[y],x+1,y)-get_sum(rt[x],x+1,y));
}
}
D. Game of Pairs
首先n为偶数很simple.前n个中间划一刀,后n个中间划一刀,两两连边就可以,可以证明无论怎么取%n!=0
其次是n为奇数,首先要证明一个东西,如果选出一些数mod 2n=n,那么可以转化为mod 2n=0,因为总和mod2n = n,所以每个pair选另外一个就可以满足.
我们考虑构造一种方案使得%n下选出的是0到n-1,一个pair两个中只能选一个,%n同余的两个数只能选一个,那么就有2n个点2n条边,且每个点度数为2,两条边的种类不同,所以必定形成了多个简单偶数环,在这些偶数环中,我们只要隔一个选一个出来就可以
#include<bits/stdc++.h>
using namespace std;
const int N=1000010;
int n;
int b[N];
pair<int,int> a[N];
struct edge{
int y,nex;
}s[N<<1];
int first[N],len=0,ans[N];
long long tot=0;
bool vis[N];
void ins(int x,int y){
s[++len]=(edge){y,first[x]};first[x]=len;
}
void dfs(int x,int fa,int dep){
if(vis[x]) return ;
vis[x]=true;if(dep&1) ans[++ans[0]]=x,tot+=x;
for(int i=first[x];i!=0;i=s[i].nex) if(s[i].y!=fa)
dfs(s[i].y,x,dep+1);
}
int main(){
scanf("%d",&n);
int k=0,l=1;
while(n%2==0) n/=2,l*=2,k++;
if(k>0){
printf("First\n");fflush(stdout);
for(int i=1;i<=n;i++){
for(int j=1;j<=l;j++) printf("%d ",(i-1)*l+j);
for(int j=1;j<=l;j++) printf("%d ",(i-1)*l+j);
}
}
else{
printf("Second\n");fflush(stdout);
for(int i=1;i<=2*n;i++){
scanf("%d",&b[i]);
if(!a[b[i]].first) a[b[i]].first=i;
else a[b[i]].second=i;
}
for(int i=1;i<=n;i++)
ins(a[i].first,a[i].second),ins(a[i].second,a[i].first);
for(int i=1;i<=n;i++) ins(i,i+n),ins(i+n,i);
for(int i=1;i<=2*n;i++) if(!vis[i]) dfs(i,0,0);
if(tot%(2*n)==0) for(int i=1;i<=n;i++) printf("%d ",ans[i]);
else for(int i=1;i<=n;i++){
if(ans[i]==a[b[ans[i]]].first) printf("%d ",a[b[ans[i]]].second);
else printf("%d ",a[b[ans[i]]].first);
}
}
fflush(stdout);
scanf("%d",&n);
}
E. Bricks
赛后看到就识别原题了.
直接考虑网络流,S连到i表示割掉这条边 i选横着,i连到T表示割掉这条边 i选竖着,流量为o,o是一个足够大的数,对于两个横着相邻的点,我们先加上他们的贡献(也就是-1),然后在网络流里面新建一个点,将两个点连向它流量为inf,这个点连向汇点,表示如果有一个点选了竖着,那么这两点的贡献就不算数,要减去他们的贡献,也就是加上1,最后答案就是dinic()-点数*o+贡献.
实验证明不连o也是可以的,因为连o主要是为了保证两边不会同时被割,但是一个点要么属于S集要么属于T集,两边不可能同时被割,然后这个本质上就是其他题解的最大独立集?
#include<bits/stdc++.h>
using namespace std;
const int N=120010;
int n,m;
struct edge{
int y,nex,c;
}s[N*8];
char ch[210][210];
bool tf[210][210];
int first[N],head[N],len=1,tot,bg,ed,op,d[N],o;
int fx[4]={-1,1,0,0};
int fy[4]={0,0,-1,1};
queue<int> q;
void ins(int x,int y,int c){
s[++len]=(edge){y,first[x],c};first[x]=len;
s[++len]=(edge){x,first[y],0};first[y]=len;
}
int pos(int x,int y){
return (x-1)*m+y;
}
bool bfs(){
memset(d,0,sizeof(d));d[bg]=1;
q.push(bg);
for(int i=1;i<=ed;i++) first[i]=head[i];
while(!q.empty()){
int x=q.front();q.pop();
for(int i=first[x];i!=0;i=s[i].nex) if(!d[s[i].y] && s[i].c){
d[s[i].y]=d[x]+1;
q.push(s[i].y);
}
}
return d[ed]!=0;
}
int dfs(int x,int t){
if(x==ed) return t;
int tot=0;
for(int&i=first[x];i!=0;i=s[i].nex) if(s[i].c && d[s[i].y]==d[x]+1){
int my=dfs(s[i].y,min(t-tot,s[i].c));
s[i].c-=my;s[i^1].c+=my;tot+=my;
if(t==tot) break;
}
return tot;
}
int dinic(){
int dx=0,ans=0;
while(bfs()){
dx=dfs(bg,1e9);
while(dx) ans+=dx,dx=dfs(bg,1e9);
}
return ans;
}
int main(){
scanf("%d %d",&n,&m);o=3*n*m;bg=o+1;ed=o+2;
for(int i=1;i<=n;i++) scanf("%s",ch[i]+1);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++) if(ch[i][j]=='#'){
tot++;
if(ch[i][j+1]=='#'){
tot--;
ins(pos(i,j),n*m+pos(i,j),1e9);
ins(pos(i,j+1),n*m+pos(i,j),1e9);
ins(n*m+pos(i,j),ed,1);
}
if(ch[i+1][j]=='#'){
tot--;
ins(2*n*m+pos(i,j),pos(i,j),1e9);
ins(2*n*m+pos(i,j),pos(i+1,j),1e9);
ins(bg,2*n*m+pos(i,j),1);
}
}
for(int i=1;i<=ed;i++) head[i]=first[i];
printf("%d\n",dinic()+tot);
}
总结
这次比赛只做了前三题,因为第三题想到一半,后面都想错了,最后5分钟才发现可以直接看前面ans[i]来二分,所以后面两题看都没看,以后要吸取经验,想清楚点,t4赛后一个半小时莽出来了,t5也是赛后才看到题发现是原题,但网络流最小割的思想也有待复习.

本文深入解析了五道算法竞赛题目,包括平衡字符串、树标签、固定点移除、对的游戏和砖块问题,涵盖字符串处理、图论、数据结构和网络流等关键概念。
432

被折叠的 条评论
为什么被折叠?



