Examples

USACO22 JAN 做题记录

Cu

T1

暴力题。

#include<stdio.h>
#include<iostream>
using namespace std;
const int maxk=27;
int ans1,ans2;
int cnt[maxk],ok[4][4];
string s[4],t[4];
int main(){
	cin>>s[1]>>s[2]>>s[3]>>t[1]>>t[2]>>t[3];
	for(int i=1;i<=3;i++)
		for(int j=0;j<3;j++){
			if(s[i][j]==t[i][j])
				ans1++,ok[i][j]=1;
			else cnt[s[i][j]-64]++;
		}
	for(int i=1;i<=3;i++)
		for(int j=0;j<3;j++)
			if(ok[i][j]==0&&cnt[t[i][j]-64])
				cnt[t[i][j]-64]--,ans2++;
	printf("%d\n%d\n",ans1,ans2);
	return 0;
}

T2

暴力题。

#include<stdio.h>
#include<iostream>
using namespace std;
int T;
int a[3][5];
int calc(int x,int y){
	int win=0,lose=0;
	for(int i=1;i<=4;i++)
		for(int j=1;j<=4;j++)
			win+=a[x][i]>a[y][j],lose+=a[x][i]<a[y][j];
	return win>lose;
}
int main(){
	scanf("%d",&T);
	while(T--){
		int ans=0;
		scanf("%d%d%d%d%d%d%d%d",&a[0][1],&a[0][2],&a[0][3],&a[0][4],&a[1][1],&a[1][2],&a[1][3],&a[1][4]);
		if(calc(0,1)==0)
			for(int i=1;i<=4;i++)
				swap(a[0][i],a[1][i]);
		if(calc(0,1)==0){
			puts("no");
			continue;
		}
		for(int i=1;i<=10;i++)
			for(int j=i;j<=10;j++)
				for(int k=j;k<=10;k++)
					for(int l=k;l<=10;l++){
						a[2][1]=i,a[2][2]=j,a[2][3]=k,a[2][4]=l;
						if(calc(1,2)&&calc(2,0))
							ans=1;
					}
		puts(ans==1? "yes":"no");
	}
	return 0;
}

T3

好难。

相邻减一相当于把 \(x\) 位置的差分数组加一,\(x-2\) 位置的差分数组减一,还有两种特殊情况,一个是位置 \(3\) 加一,一个是位置 \(n-2\) 减一。

从后往前模拟即可,时间复杂度 \(O(n)\)

#include<stdio.h>
#include<math.h>
const int maxn=100005;
int T,n;
int a[maxn];
long long b[maxn];
inline int min(int a,int b){
	return a<b? a:b;
}
int main(){
	scanf("%d",&T);
	while(T--){
		long long ans=0;
		scanf("%d",&n);
		for(int i=1;i<=n;i++)
			scanf("%d",&a[i]);
		for(int i=1;i<=n;i++)
			b[i]=a[i]-a[i-1];
		int flg=0;
		for(int i=n;i>=2;i--){
			if(b[i]>0)
				if(((n-i)&1)==0)
					flg=1;
			if(b[i]<0)
				b[i-2]+=b[i];
		}
		for(int i=1;i<=n;i++)
			ans+=a[i]-b[1];
		if(b[1]<0||b[0]<0||flg)
			puts("-1");
		else printf("%lld\n",ans);
		b[0]=0;
	}
	return 0;
}

Ag

T1

感觉是全场最困难的题目。

考虑最优的行动是分四段:

先加若干次,然后若干个除(若当前数末尾是一,则先进行一次加),再加若干次,然后再是若干个乘(若目标数末尾是一,则之后进行一次加)。

直接枚举每次操作次数即可,复杂度 \(O(\log n)\)

#include<stdio.h>
int T,ans;
long long A,B;
int main(){
	scanf("%d",&T);
	while(T--){
		scanf("%lld%lld",&A,&B),ans=10000;
		for(int i=0;i<=10;i++)
			for(int j=0;j<=60;j++)
				for(int k=0;k<=10;k++){
					int res=i;
					long long now=A+i;
					for(int p=1;p<=j;p++){
						if(now&1)
							now++,res++;
						now>>=1,res++;
					}
					now+=k,res+=k;
					int tot=0;
					long long tmp=now;
					while((tmp<<1)<=B)
						tmp<<=1,tot++;
					if((B>>tot)!=now)
						continue;
					for(int p=1;p<=tot;p++){
						now<<=1,res++;
						if((B>>(tot-p))&1)
							now++,res++;
					}
					if(res<ans)
						ans=res;
				}
		printf("%d\n",ans);
	}
	return 0;
}

T2

签到题。

考虑满足 \(\forall_{i<k<j} h_k<\min\{h_i,h_j\}\)\((i,j)\) 对在笛卡尔树上的形态是一个点以及其左儿子的右链,或者是其右儿子的左链,直接在树上搜一遍就好了。

复杂度 \(O(n)\)

#include<stdio.h>
const int maxn=300005;
int n,top;
int h[maxn],lc[maxn],rc[maxn],stk[maxn],totl[maxn],totr[maxn];
long long ans;
long long suml[maxn],sumr[maxn];
void dfs(int x){
	if(lc[x]){
		dfs(lc[x]),ans+=1ll*totr[lc[x]]*(x+1)-sumr[lc[x]];
		totl[x]=totl[lc[x]]+1,suml[x]=suml[lc[x]]+x;
	}
	else suml[x]=x,totl[x]=1;
	if(rc[x]){
		dfs(rc[x]),ans+=suml[rc[x]]-1ll*totl[rc[x]]*(x-1);
		totr[x]=totr[rc[x]]+1,sumr[x]=sumr[rc[x]]+x;
	}
	else sumr[x]=x,totr[x]=1;
}
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
		scanf("%d",&h[i]);
		int k=top;
		while(k>0&&h[stk[k]]<=h[i])
			k--;
		if(k>0)
			rc[stk[k]]=i;
		if(k<top)
			lc[i]=stk[k+1];
		top=k,stk[++top]=i;
	}
	dfs(stk[1]),printf("%lld\n",ans);
	return 0;
}

T3

有一点点困难。

考虑将一只奶牛最喜欢的两种麦片连边,对于每个连通块,其答案的上界一定是点数和边数的 \(\max\),下面给出一种构造:

找这个连通块的一个 dfs 树,找到其一个环(若没有则任意钦定一个单点),先取环上的一条边,那么剩下的边都可以按顺序取。然后从这个环(点)向外搜索出一个基环树(树)的形态即可。

复杂度 \(O(n)\)

#include<stdio.h>
#include<vector>
#include<queue>
#include<algorithm>
#include<assert.h>
using namespace std;
const int maxn=200005;
int n,m,ans,flg;
int vis[maxn],vvis[maxn],ok[maxn],fa[maxn],rec[maxn],fir[maxn],sec[maxn],dep[maxn],tr[maxn];
vector<int>tmp,V,res,v[maxn],w[maxn];
queue<int>q;
inline void add(int x,int y,int i){
	v[x].push_back(y),w[x].push_back(i);
}
void dfs(int x,int last){
	fa[x]=last,vis[x]=1,dep[x]=dep[last]+1;
	for(int i=0;i<v[x].size();i++){
		int y=v[x][i],k=w[x][i];
		if(vis[y]==0)
			tr[k]=1,rec[y]=k,dfs(y,x);
		else if(y!=last&&flg==0){
			int recflg=0;
			if(dep[x]<dep[y])
				swap(x,y),recflg=1;
			flg=1,tmp.clear(),V.clear(),V.push_back(y);
			for(int j=x;j!=y;j=fa[j])
				V.push_back(j),tmp.push_back(rec[j]);
			if(fir[w[x][i]]==y)
				reverse(tmp.begin(),tmp.end());
			ok[k]=1,res.push_back(k);
			for(int j=0;j<tmp.size();j++)
				ok[tmp[j]]=1,res.push_back(tmp[j]);
			if(recflg)
				swap(x,y);
		}
	}
}
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1,x,y;i<=n;i++)
		scanf("%d%d",&x,&y),fir[i]=x,sec[i]=y,add(x,y,i),add(y,x,i);
	for(int i=1;i<=m;i++)
		if(vis[i]==0){
			flg=0,dfs(i,0);
			if(flg)
				for(int j=0;j<V.size();j++)
					vvis[V[j]]=1,q.push(V[j]);
			else vvis[i]=0,q.push(i);
			while(!q.empty()){
				int x=q.front();
				q.pop();
				for(int j=0;j<v[x].size();j++)
					if(tr[w[x][j]]&&vvis[v[x][j]]==0){
						int y=v[x][j];
						vvis[y]=1,q.push(y);
						if(ok[w[x][j]]==0)
							res.push_back(w[x][j]),ok[w[x][j]]=1;
					}
			}
		}
	for(int i=1;i<=n;i++)
		if(ok[i]==0)
			res.push_back(i);
	for(int i=1;i<=m;i++)
		vis[i]=0;
	for(int i=0;i<res.size();i++){
		int x=res[i];
		if(vis[fir[x]]==0)
			vis[fir[x]]=1,ans++;
		else if(vis[sec[x]]==0)
			vis[sec[x]]=1,ans++;
	}
	printf("%d\n",n-ans);
	for(int i=0;i<res.size();i++)
		printf("%d\n",res[i]);
	return 0;
}

Au

T1

太困难了。

时光倒流,相当于一开始所有数相等,你每次给相邻位置加一,每个位置不能超过上界,求能得到多少序列。

假设所有数的初值是固定的(设其为 \(st\)),我们就可以直接 dp,令 \(f_{i,j}\) 表示选到第 \(i\) 个位置,其中 \((i,i+1)\) 选了 \(j\) 次的合法序列数,那么可以得到:

\[f_{i+1,k}=\sum_{j=0}^{\min\{h_i-st,h_{i+1}-st-k\}} f_{i,j} \]

前缀和优化即可做到 \(O(nV)\)

\(n\) 为偶数的时候,\(st\) 可以直接是 \(0\),否则 \(st\) 可以设为 \(0,1,\cdots,\min h_i\) 的任意数。

总时间复杂度 \(O(nV^2)\)

#include<stdio.h>
#include<iostream>
#include<string.h>
using namespace std;
const int maxn=105,maxv=1005,mod=1000000007;
int n,ans,up,mn=100000;
int h[maxn],f[maxn][maxv],g[maxn][maxv];
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
		scanf("%d",&h[i]),mn=min(mn,h[i]);
	h[n+1]=maxv-1;
	if(n%2==0)
		up=0;
	else up=mn;
	for(int st=0;st<=up;st++){
		memset(f,0,sizeof(f)),memset(g,0,sizeof(g));
		for(int i=0;i<=h[1]-st&&i<=h[2]-st;i++)
			f[1][i]=1;
		for(int i=1;i<n-1;i++){
			g[i][0]=f[i][0];
			for(int j=1;j<=h[i]-st&&j<=h[i+1]-st;j++)
				g[i][j]=(g[i][j-1]+f[i][j])%mod;
			for(int k=0;k<=h[i+1]-st&&k<=h[i+2]-st;k++){
				int j=min(min(h[i]-st,h[i+1]-st),h[i+1]-st-k);
				f[i+1][k]=g[i][j];
			}
		}
		for(int i=0;i<maxv;i++)
			ans=(ans+f[n-1][i])%mod;
	}
	printf("%d\n",ans);
	return 0;
}

T2

漏看了一个条件,居然还做出来了。

考虑时光倒流,然后线段树分治,那么我们对于每个数,找到其第一次合法的时间。

我们将每个点拆点,\(A\)\(B\),初始一个点的 \(A\)\(B\) 连边。合并两个并查集先特判是否有一个连通块是合法的,然后将两个并查集的 \(A\) 连边,撤销的时候给边懒删除即可。

当一个点变成 active 之后,我们就将整个连通块遍历一遍,将遍历到的 \(B\) 点统计答案,并将整个连通块清空,这样可以均摊分析,时间复杂度为 \(O(n\log n\alpha(n))\)

代码有些细节写错了,但是没有把我卡掉,我就懒得改了。

#include<stdio.h>
#include<map>
#include<vector>
#define lc(x) (x<<1)
#define rc(x) (x<<1|1)
#define mid (l+r>>1)
using namespace std;
const int maxn=1000005;
int n,q,top,tot,e,stp;
int xx[maxn],yy[maxn],es[maxn],et[maxn],dsu[maxn],stk[maxn],sz[maxn],start[maxn],to[maxn],then[maxn],ans[maxn],vis[maxn],mn[maxn],del[maxn];
char c;
map<pair<int,int>,int>mp;
vector<int>edg[maxn],fam[maxn];
inline void add(int x,int y){
	then[++e]=start[x],start[x]=e,to[e]=y;
}
void dfs(int x,int v){
	if(x<=n)
		ans[x]=v;
	vis[x]=stp;
	for(int i=start[x];i;i=then[i])
		if(vis[to[i]]!=stp)
			dfs(to[i],v);
	start[x]=0;
}
int find(int x){
	return dsu[x]==x? x:find(dsu[x]);
}
void merge(int a,int b,int t){
	a=find(a),b=find(b);
	if(a==b)
		return ;
	if(sz[a]>sz[b])
		swap(a,b);
	dsu[a]=b,sz[b]+=sz[a],stk[++top]=a,mn[b]=min(mn[b],mn[a]);
	t=max(t,mn[b]);
	if(start[n+a]==0&&start[n+b])
		stp++,dfs(n+b,t);
	if(start[n+b]==0&&start[n+a])
		stp++,dfs(n+a,t);
	if(start[n+a]&&start[n+b])
		add(n+a,n+b),add(n+b,n+a);
}
void cancel(int rec){
	while(top>rec)
		sz[find(stk[top])]-=sz[stk[top]],dsu[stk[top]]=stk[top],top--;
}
void insert(int l,int r,int now,int L,int R,int v){
	if(L<=l&&r<=R){
		edg[now].push_back(v);
		return ;
	}
	if(L<=mid)
		insert(l,mid,lc(now),L,R,v);
	if(mid<R)
		insert(mid+1,r,rc(now),L,R,v);
}
void solve(int l,int r,int now){
	int rec=top;
	if(l==r)
		for(int i=0;i<fam[l].size();i++)
			stp++,dfs(n+find(fam[l][i]),l);
	for(int i=0;i<edg[now].size();i++)
		merge(xx[edg[now][i]],yy[edg[now][i]],q-et[edg[now][i]]+1);
	if(l!=r)
		solve(l,mid,lc(now)),solve(mid+1,r,rc(now));
	cancel(rec);
}
int main(){
	scanf("%d%d",&n,&q);
	for(int i=1;i<=n*2;i++)
		dsu[i]=i,sz[i]=1;
	for(int i=1;i<=n;i++)
		add(i,n+i),add(n+i,i);
	for(int i=1,x,y;i<=q;i++){
		for(c=getchar();c!='A'&&c!='D'&&c!='R';c=getchar());
		if(c=='D')
			scanf("%d",&x),fam[q-(i-1)+1].push_back(x),del[x]=1,mn[x]=n-(i-1)+1;
		if(c=='A')
			scanf("%d%d",&x,&y),mp[make_pair(x,y)]=++tot,xx[tot]=x,yy[tot]=y,es[tot]=i;
		if(c=='R')
			scanf("%d",&x),et[x]=i-1,mp[make_pair(xx[x],yy[x])]=0;
	}
	for(int i=1;i<=n;i++)
		if(del[i]==0)
			fam[1].push_back(i),mn[i]=0;
	for(int i=1;i<=tot;i++){
		if(et[i]==0)
			et[i]=q;
		insert(1,q,1,q-et[i]+1,q-es[i]+1,i);
	}
	solve(1,q,1);
	for(int i=1;i<=n;i++){
		if(ans[i]==0)
			puts("0");
		else printf("%d\n",q-ans[i]+1);
	}
	return 0;
}

T3

赛场不会。

令题面中 \(j\) 数组为 \(b\) 数组。

首先有一个简单的差分约束:

\[a_i\geqslant a_{i-1}\\a_{b_i}-a_i\leqslant k\\a_{b_i+1}-a_i>k \]

可以发现 \(k\) 越大一定是越优的,于是我们直接令 \(k=10^9\),设 \(a_i=x_ik+y_i\)\(0\leqslant y_i<k\))。

上面的差分约束可以转化为:

\(x_i-x_{i-1}\geqslant 0\),若 \(x_i-x_{i-1}=0\),则有 \(y_i-y_{i-1}\geqslant 0\)

\(x_i-x_{b_i}\geqslant -1\),若 \(x_i-x_{b_i}=-1\),则有 \(y_i-y_{b_i}\geqslant 0\)

\(x_{b_i+1}-x_i\geqslant 1\),若 \(x_{b_i+1}-x_i=1\),则有 \(y_{b_i+1}-y_i\geqslant 1\)

我们可以考虑先构造出 \(x\),再通过 \(x\) 构造一个差分约束模型,不难发现由于边权是 \(0/-1\) 且没有负环,所以所有环都是零环,缩点+拓扑排序即可解决。

事实上很容易给出一组 \(x\) 的构造:

令变量 \(p=1\),并不断执行以下操作:

设当前轮数为 \(d\),将 \([p,b_p]\) 设为 \(d\),再令 \(p\leftarrow b_p+1\)

第一个限制和第三个限制显然满足,考虑为什么满足第二个限制:

我们设覆盖位置 \(i\)\(p\)\(p_0\),则由于 \(b_{p_0}\geqslant i\),而 \(b\) 又是递增的,所以有 \(b_{b_{p_0}+1}\geqslant b_i\),也就是至多下一轮 \(b_i\) 就会被覆盖。

时间复杂度 \(O(n)\)

#include<stdio.h>
#include<queue>
#include<vector>
using namespace std;
const int maxn=100005,K=1000000000;
int n,top,dfns,tot,es;
int b[maxn],X[maxn],Y[maxn],deg[maxn],stk[maxn],dfn[maxn],low[maxn],vis[maxn],bel[maxn],xx[maxn*3],yy[maxn*3],zz[maxn*3];
queue<int>q;
vector<int>v[maxn],g[maxn];
inline void add(int x,int y,int z){
	if(z==0)
		v[x].push_back(y);
	es++,xx[es]=x,yy[es]=y,zz[es]=z;
}
void tarjan(int x){
	dfn[x]=low[x]=++dfns,vis[x]=1,stk[++top]=x;
	for(int i=0;i<v[x].size();i++){
		int y=v[x][i];
		if(dfn[y]==0)
			tarjan(y),low[x]=min(low[x],low[y]);
		else if(vis[y])
			low[x]=min(low[x],dfn[y]);
	}
	if(dfn[x]==low[x]){
		int y=0;
		tot++;
		while(y!=x)
			y=stk[top],top--,bel[y]=tot,vis[y]=0;
	}
}
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
		scanf("%d",&b[i]);
	int pos=1,d=1;
	while(pos<=n){
		for(int i=pos;i<=b[pos];i++)
			X[i]=d;
		pos=b[pos]+1,d++;
	}
	for(int i=2;i<=n;i++)
		if(X[i]==X[i-1])
			add(i,i-1,0);
	for(int i=1;i<=n;i++){
		if(X[i]-X[b[i]]==-1)
			add(i,b[i],0);
		if(b[i]<n&&X[b[i]+1]-X[i]==1)
			add(b[i]+1,i,-1);
	}
	for(int i=1;i<=n;i++)
		if(dfn[i]==0)
			tarjan(i);
	for(int i=1;i<=es;i++)
		if(bel[xx[i]]!=bel[yy[i]])
			g[bel[xx[i]]].push_back(i),deg[bel[yy[i]]]++;
	for(int i=1;i<=tot;i++)
		if(deg[i]==0)
			q.push(i);
	while(!q.empty()){
		int x=q.front();
		q.pop();
		for(int i=0;i<g[x].size();i++){
			int k=g[x][i],y=bel[xx[k]]+bel[yy[k]]-x;
			deg[y]--,Y[y]=min(Y[y],Y[x]+zz[k]);
			if(deg[y]==0)
				q.push(y);
		}
	}
	printf("%d\n",K);
	for(int i=1;i<=n;i++)
		printf("%lld\n",1ll*X[i]*K+Y[bel[i]]);
	return 0;
}

Pt

posted @ 2022-02-02 16:03  xiaoziyao  阅读(81)  评论(0编辑  收藏  举报