USACO2019 DEC 部分題解


因為一些翻譯問題鉑金只過了 B 。之后看只剩余大約 1h 於是棄療了。

但是某沙雕同學提供的假翻譯似乎也挺能做而且還算有趣,以后可能會把它出出來?也算不錯了,膜拜帶翻譯家

不過並不能改變最后排名墊底了。

后來想想感覺 A 還是很能做的

C 是真的不會,考后找某集訓隊學長請教,結果只學會了部分分的做法。。。

以上是廢話

Bronze

三個普及組題。按題意模擬即可

代碼懶得放了。。。

Silver

A

求第 \(k\) 個既不被 \(3\) 整除,也不被 \(5\) 整除的數字

\(k\le 10^9\)

發現 \(15\) 個形成一個循環,邊角直接加上去就行了

#include <bits/stdc++.h>
using namespace std;
int l[]={0,1,2,4,7,8,11,13,14};
int main (){
	freopen ("moobuzz.in","r",stdin);
	freopen ("moobuzz.out","w",stdout);
	int n;scanf ("%d",&n);
	int m=(n-1)/8;
	printf ("%d",m*15+l[(n-1)%8+1]);
	return 0;
}

B

長度為 \(L\) 的數軸上有 \(n\) 個點,每個點有權值和左/右的方向,每個時刻一個單位的移動。若兩個點碰撞則交換方向(並不交換權值)后繼續運動,若一個點到了 \(0\)\(L\) 則停止運動。問停止運動的權值之和恰好大於 \(\frac{\sum w_i}{2}\) 的時候發生了多少次碰撞。

\(L\le 10^9,n\le 5\times 10^4,w_i\le 10^3\)

挺有意思的一個題,這一類題一般都是看似交換,實則當成不交換處理。一開始以為這題也可以直接無視交換方向,后來發現權值這種東西並不能直接這樣搞。。。

考慮一個向右的和一串向左的連續碰撞之后的結果,應該是等價於把向右的那個反向,一串向左的最后一個反向,剩余的不受影響。

推廣一下這個結論,假設不考慮碰撞,有 \(x\) 個點可以到達 \(0\) ,那么這 \(x\) 個點的權值之和一定是從左往右的前 \(x\) 個之和,證明比較顯然。到 \(L\) 的也同理。

所以可以二分答案找出這個時刻,后面求碰撞次數就可以無視交換權值方向直接做了,具體實現可以用二分。

不知道為什么出題人放 \(n^2\) 過了

#include <bits/stdc++.h>
using namespace std;
const int N=5e4+5;
int n,L,sum=0;
int w[N],x[N],d[N];
struct Node{
	int pos,w;
}a[N];
bool check(int md){
	int S=0;int c1=0,c2=0;
	for (int i=1;i<=n;i++)
		if (x[i]+d[i]*md<=0) c1++;
		else if (x[i]+d[i]*md>=L) c2++;
	for (int i=1;i<=c1;i++) S+=a[i].w;
	for (int i=1;i<=c2;i++) S+=a[n-i+1].w;
	return S*2>=sum;
}
bool cmp(Node a,Node b){
	return a.pos<b.pos;
}
int p1[N],p2[N];
int main (){
	freopen ("meetings.in","r",stdin);
	freopen ("meetings.out","w",stdout);
	scanf ("%d%d",&n,&L);
	int c1=0,c2=0;
	for (int i=1;i<=n;i++){
		scanf ("%d%d%d",&w[i],&x[i],&d[i]);sum+=w[i];
		if (d[i]==1) p1[++c1]=x[i];
		else p2[++c2]=x[i];
		a[i]=(Node){x[i],w[i]};
	}
	sort(a+1,a+n+1,cmp);
	sort(p1+1,p1+c1+1);
	sort(p2+1,p2+c2+1);
	int l=0,r=L,T;
	while (l<=r){
		int mid=(l+r)>>1;
		if (check(mid)) T=mid,r=mid-1;
		else l=mid+1;
	}
	long long ans=0;
	for (int i=1;i<=c1;i++){
		l=lower_bound(p2+1,p2+c2+1,p1[i])-p2;
		r=upper_bound(p2+1,p2+c2+1,p1[i]+2*T)-p2-1;
		ans+=r-l+1;
	}
	printf ("%lld",ans);
	return 0;
}

C

給一棵樹點權為 \(0/1\)\(q\) 次詢問路徑 \((u,v)\) 上有沒有 \(0/1\)

\(n\le 10^5,q\le 10^5\)

一個挺無聊的題,感覺已經被出爛了。可以直接倍增維護是否存在,記向上 \(2^k\) 步有沒有 \(0/1\) ,求 LCA 的時候順便搞出來就行了

#include <bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int fa[20][N];
bool G[20][N],H[20][N];
char s[N];
int Head[N],Next[N<<1],Adj[N<<1],tot=0;
void addedge(int u,int v){
	Next[++tot]=Head[u],Head[u]=tot,Adj[tot]=v;
	Next[++tot]=Head[v],Head[v]=tot,Adj[tot]=u;
}
int deep[N];
void dfs(int x,int f){
	fa[0][x]=f;
	for (int e=Head[x];e;e=Next[e])
		if (Adj[e]!=f) deep[Adj[e]]=deep[x]+1,dfs(Adj[e],x);
}
int main (){
	freopen ("milkvisits.in","r",stdin);
	freopen ("milkvisits.out","w",stdout);
	int n,m;cin>>n>>m;
	cin>>(s+1);
	for (int i=1;i<=n;i++)
		if (s[i]=='H') H[0][i]=1;
		else G[0][i]=1;
	for (int i=1,u,v;i<n;i++) {
		cin>>u>>v;
		addedge(u,v);
	}
	deep[1]=1;
	dfs(1,0);
	for (int j=1;j<=18;j++)
		for (int i=1;i<=n;i++){
			fa[j][i]=fa[j-1][fa[j-1][i]];
			G[j][i]=G[j-1][i]|G[j-1][fa[j-1][i]];
			H[j][i]=H[j-1][i]|H[j-1][fa[j-1][i]];
		}
	while (m--){
		int u,v;char c;cin>>u>>v>>c;
		if (deep[u]<deep[v]) swap(u,v);
		if (c=='G') {
			bool ans=false;
			for (int j=18;j>=0;j--)
				if (deep[fa[j][u]]>=deep[v])
					ans|=G[j][u],u=fa[j][u];
			ans|=G[0][u];
			if (u!=v){
				for (int j=18;j>=0;j--)
					if (fa[j][u]!=fa[j][v])
						ans|=G[j][u]|G[j][v],u=fa[j][u],v=fa[j][v];
				ans|=G[0][fa[0][u]],ans|=G[0][u],ans|=G[0][v];
			}
			printf ("%d",ans);
		}else{
			bool ans=false;
			for (int j=18;j>=0;j--)
				if (deep[fa[j][u]]>=deep[v])
					ans|=H[j][u],u=fa[j][u];
			ans|=H[0][u];
			if (u!=v){
				for (int j=18;j>=0;j--)
					if (fa[j][u]!=fa[j][v])
						ans|=H[j][u]|H[j][v],u=fa[j][u],v=fa[j][v];
				ans|=H[0][fa[0][u]],ans|=H[0][u],ans|=H[0][v];
			}
			printf ("%d",ans);
		}
	}
	return 0;
}

Gold

A

給一張圖,每條邊有流量限制和邊權,選一條 \(1\)\(N\) 路徑,使得\(\frac{\min\{f_i\}}{\sum w_i}\) 最大,輸出這個值

\(n\le 10^3,m\le 10^3,f_i\le 10^3\)

沒想太多。。看到流量很小,可以枚舉流量,然后在原圖上跑 dijkstra,復雜度 \(O(fm\log m)\) 。不知道有沒有高論

#include <bits/stdc++.h>
using namespace std;
const int N=1005,E=2005;
struct Edge{
	int u,v,f,w;
}e[E];
int n,m;
int dis[N];bool vis[N];
int Head[N],Next[E],Adj[E],Weight[E],tot=0;
void addedge(int u,int v,int w){
	Next[++tot]=Head[u],Head[u]=tot,Adj[tot]=v,Weight[tot]=w;
	Next[++tot]=Head[v],Head[v]=tot,Adj[tot]=u,Weight[tot]=w;
}
priority_queue < pair <int,int> > Q;
int dijkstra(int lim){
	memset (vis,0,sizeof(vis));
	memset (dis,0x3f,sizeof(dis));
	memset (Head,0,sizeof(Head));
	memset (Next,0,sizeof(Next));
	memset (Adj,0,sizeof(Adj));
	memset (Weight,0,sizeof(Weight));tot=0;
	Q.push(make_pair(0,1));
	dis[1]=0;
	for (int i=1;i<=m;i++)
		if (e[i].f>=lim) addedge(e[i].u,e[i].v,e[i].w);
	while (!Q.empty()){
		int x=Q.top().second;Q.pop();
		if (vis[x]) continue;vis[x]=true;
		for (int e=Head[x];e;e=Next[e])
			if (dis[x]+Weight[e]<dis[Adj[e]]){
				dis[Adj[e]]=dis[x]+Weight[e];
				Q.push(make_pair(-dis[Adj[e]],Adj[e]));
			}
	}
	return dis[n];
}
int main (){
	freopen ("pump.in","r",stdin);
	freopen ("pump.out","w",stdout);
	scanf ("%d%d",&n,&m);
	for (int i=1;i<=m;i++) scanf ("%d%d%d%d",&e[i].u,&e[i].v,&e[i].w,&e[i].f);
	double ans=0;
	for (int i=1;i<=1000;i++)
		ans=max(ans,(double)i/(double)dijkstra(i));
	printf ("%lld",(long long)(ans*1000000));
	return 0;
}

B

銀組 t3 加強版,把兩種屬性變成了 \(10^5\)

\(n\le 10^5,m\le 10^5\)

比賽時寫了個很蠢的 \(n\log^2n\) 的做法,因為懶得重寫直接把以前代碼拷來改改就行了,用 \(c\) 棵動態開點線段樹維護所有顏色,然后直接樹剖在對應線段樹上看有沒有點即可

另一個很好寫的單 \(\log\) 做法:樹上差分,用主席樹維護每個節點到根每種顏色出現次數,查詢可以用兩個點的數量減去 LCA 和 LCA父親 處的數量,判斷是否為 \(0\) 即可

因為沒有強制在線,或許能有更低的復雜度?

#include <bits/stdc++.h>
using namespace std;
const int N=100005;
int Head[N],Next[N<<1],Adj[N<<1],tot=0;
void addedge(int u,int v){
	Next[++tot]=Head[u],Head[u]=tot,Adj[tot]=v;
	Next[++tot]=Head[v],Head[v]=tot,Adj[tot]=u;
}
int fa[N],deep[N],son[N],size[N],top[N];
void dfs(int x,int f){
	size[x]=1;
	for (int e=Head[x];e;e=Next[e])
		if (Adj[e]!=f){
			fa[Adj[e]]=x;
			deep[Adj[e]]=deep[x]+1;
			dfs(Adj[e],x);
			size[x]+=size[Adj[e]];
			son[x]=(size[son[x]]<size[Adj[e]]?Adj[e]:son[x]);
		}
}
int dfn[N],to[N],Time=0;
void dfs2(int x,int tp){
	top[x]=tp;dfn[x]=++Time,to[Time]=x;
	if (!son[x]) return;dfs2(son[x],tp);
	for (int e=Head[x];e;e=Next[e])
		if (Adj[e]!=fa[x]&&Adj[e]!=son[x])
			dfs2(Adj[e],Adj[e]);
}
int rt[N];
int ls[N*30],rs[N*30];
int cnt=0;
#define mid ((l+r)>>1)
void insert(int&root,int l,int r,int x){
	if (!root) root=++cnt;
	if (l==r) return;
	if (x<=mid) insert(ls[root],l,mid,x);
	else insert(rs[root],mid+1,r,x);
}
bool query(int root,int l,int r,int L,int R){
	if ((!root)||r<L||l>R) return false;
	if (L<=l&&r<=R) return true;
	return query(ls[root],l,mid,L,R)||query(rs[root],mid+1,r,L,R);
}
int s[N];
int main (){
	freopen ("milkvisits.in","r",stdin);
	freopen ("milkvisits.out","w",stdout);
	int n,m;scanf ("%d%d",&n,&m);
	for (int i=1;i<=n;i++) scanf ("%d",&s[i]);
	for (int i=1;i<n;i++){
		int u,v;scanf ("%d%d",&u,&v);
		addedge(u,v);
	}
	dfs(1,0),dfs2(1,1);
	for (int i=1;i<=n;i++) insert(rt[s[i]],1,n,dfn[i]);
	while (m--){
		int u,v,w;scanf ("%d%d%d",&u,&v,&w);
		bool ans=false;
		while (top[u]!=top[v]){
			if (deep[top[u]]<deep[top[v]]) swap(u,v);
			ans|=query(rt[w],1,n,dfn[top[u]],dfn[u]);
			u=fa[top[u]];
		}
		if (dfn[u]>dfn[v]) swap(u,v);
		ans|=query(rt[w],1,n,dfn[u],dfn[v]);
		printf ("%d",ans);
	}
	return 0;
}

C

給定一個字符串和一個 \(M\times M\) 的矩陣 \(a\),表示可以消耗 \(a_{i,j}\) 的代價把字符 \(i\) 改成字符 \(j\),求把原字符串改成每個連續段長度都不小於 \(K\) 的最小代價。

\(n,K\le 10^5,M\le 26\)

首先直接用原矩陣的交換方法不一定最優,拿到手先跑一遍 floyd。

然后考慮 dp ,\(f_i\) 表示前 \(i\) 個中每個連續段都 \(\ge k\) 的最小代價,顯然有轉移

\[f_i=\min\{ f_j +cost(i,j,c)\} \ \ \ (j\le i-k) \]

其中 \(cost(i,j,c)\) 表示把 \([i,j]\) 全都改成字符 \(c\) 的最小代價,這個可以前綴和預處理。

觀察轉移,發現可以在枚舉到 \(i\) 時再把 \(i-k\) 的決策加進去,這樣維護一個前綴 \(\min\) 即可做到 \(O(1)\) 轉移。

復雜度 \(O(n\times M)\)

#include <bits/stdc++.h>
using namespace std;
const int N=100005;
int c[30][30],f[N];
char s[N];
int sum[30][N];
int query(int x,int l,int r){
	return sum[x][r]-sum[x][l-1];
}
int mn[30];
int main (){
	freopen ("cowmbat.in","r",stdin);
	freopen ("cowmbat.out","w",stdout);
	int n,m,k;scanf ("%d%d%d",&n,&m,&k);
	scanf ("%s",s+1);
	for (int i=0;i<m;i++)
		for (int j=0;j<m;j++)
			scanf ("%d",&c[i][j]);
	for (int l=0;l<m;l++)
		for (int i=0;i<m;i++)
			for (int j=0;j<m;j++)
				if (i!=j&&j!=l&&i!=l)
					c[i][j]=min(c[i][j],c[i][l]+c[l][j]);
	for (int i=0;i<m;i++)
		for (int j=1;j<=n;j++)
			sum[i][j]=c[s[j]-'a'][i]+sum[i][j-1];
	memset (mn,0x3f,sizeof(mn));
	memset (f,0x3f,sizeof(f));f[0]=0;
	for (int i=k;i<=n;i++)
		for (int j=0;j<m;j++)
			mn[j]=min(mn[j]+c[s[i]-'a'][j],f[i-k]+query(j,i-k+1,i)),f[i]=min(f[i],mn[j]);
	printf ("%d",f[n]);
	return 0;
}

Platinum

B

給定一棵以 \(1\) 為根節點的樹,支持以下兩種操作:

  1. 給定 \(x,c\) ,給 \(x\) 的子樹中每個點的屬性集合里插入一種屬性 \(c\)
  2. 給定 \(x\) ,求 \(x\) 的子樹內所有點各有多少種不同的屬性,需要輸出屬性個數之和

\(n\le 10^5,q\le 10^5\)

dfs 序上子樹表示為一段連續的區間,因此兩個子樹要么包含,要么不相交

考慮修改一個點,影響的范圍是 \(x\) 的子樹,我們給這個范圍內所有點的權值都 +1

但是這樣會產生沖突,如果一個屬性已經有過了,那么就會有重復,為了消除重復下面分兩種情況:

  1. 考慮當前操作的區間被某個區間包含過,那么這是個廢區間,直接不做
  2. 如果這個區間包含之前的某些區間,那么撤銷那些區間的影響,再加上這個區間

實現方面,可以用 set 維護情況 2 並支持撤銷,操作 1 我寫了個很蠢的線段樹,現在想想大概是不必要的

這樣,查詢操作就可以做一次線段樹區間求和

時間復雜度 \(O(q\log n)\)

代碼里有很多因為假題意留下的無用的東西,並不推薦食用

#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int N=100005,mx=100000;
int Head[N],Next[N<<1],Adj[N<<1],tot=0;
void addedge(int u,int v){
	Next[++tot]=Head[u],Head[u]=tot,Adj[tot]=v;
	Next[++tot]=Head[v],Head[v]=tot,Adj[tot]=u;
}
set < pair<int,int> > S[N];
set <int> P[N];
int dfn[N],to[N],Time=0;
int deep[N],size[N],son[N],top[N],fa[N];
void dfs(int x,int f){
	size[x]=1,fa[x]=f;
	for (int e=Head[x];e;e=Next[e])
		if (Adj[e]!=f){
			deep[Adj[e]]=deep[x]+1;
			dfs(Adj[e],x);
			size[x]+=size[Adj[e]];
			son[x]=(size[son[x]]>size[Adj[e]]?son[x]:Adj[e]);
		}
}
void dfs2(int x,int tp){
	top[x]=tp;dfn[x]=++Time,to[Time]=x;
	if (!son[x]) return;
	dfs2(son[x],tp);
	for (int e=Head[x];e;e=Next[e])
		if (Adj[e]!=fa[x]&&Adj[e]!=son[x])
			dfs2(Adj[e],Adj[e]);
}
inline int LCA(int u,int v){
	if ((!u)||(!v)) return 0;
	for (;top[u]!=top[v];deep[top[u]]<deep[top[v]]?v=fa[top[v]]:u=fa[top[u]]);
	return deep[u]<deep[v]?u:v;
}
struct SegTree{
	ll val[N<<2];
	int tag[N<<2];
	#define mid ((l+r)>>1)
	void pushdown(int root,int l,int r){
		if (tag[root]){
			tag[root<<1]+=tag[root];
			tag[(root<<1)|1]+=tag[root];
			val[root<<1]+=1ll*tag[root]*(mid-l+1);
			val[(root<<1)|1]+=1ll*tag[root]*(r-mid);
			tag[root]=0;
		}
	}
	ll query(int root,int l,int r,int L,int R){
		if (r<L||l>R) return 0;
		if (L<=l&&r<=R) return val[root];
		pushdown(root,l,r);
		return query(root<<1,l,mid,L,R)+query((root<<1)|1,mid+1,r,L,R);
	}
	void update(int root,int l,int r,int L,int R,int v){
		if (r<L||l>R) return;
		if (L<=l&&r<=R){
			val[root]+=1ll*(r-l+1)*v;
			tag[root]+=v;
			return;
		}
		pushdown(root,l,r);
		update(root<<1,l,mid,L,R,v);
		update((root<<1)|1,mid+1,r,L,R,v);
		val[root]=val[root<<1]+val[(root<<1)|1];
	}
	#undef mid
}T;
int rt[N],cnt=0;
struct Seg2{
	#define mid ((l+r)>>1)
	int ls[N*30],rs[N*30];
	void insert(int&root,int l,int r,int x){
		if (!root) root=++cnt;
		if (l==r) return;
		if (x<=mid) insert(ls[root],l,mid,x);
		else insert(rs[root],mid+1,r,x);
	}
	bool used(int root,int l,int r,int L,int R){
		if ((!root)||r<L||l>R) return false;
		if (L<=l&&r<=R) return true;
		return used(ls[root],l,mid,L,R)||used(rs[root],mid+1,r,L,R);
	}
	#undef mid
}Tree;
int n,q;
bool vis(int c,int x){
	bool f=false;
	while (x){
		f|=Tree.used(rt[c],1,n,dfn[top[x]],dfn[x]);
		x=fa[top[x]];
	}
	return f;
}
void ins(int c,int x){
	Tree.insert(rt[c],1,n,dfn[x]);
}
int main (){
	freopen ("snowcow.in","r",stdin);
	freopen ("snowcow.out","w",stdout);
	scanf ("%d%d",&n,&q);
	for (int i=1;i<n;i++){
		int u,v;scanf ("%d%d",&u,&v);
		addedge(u,v);
	}
	deep[1]=1;dfs(1,0),dfs2(1,1);
	while (q--){
		int opt;scanf ("%d",&opt);
		if (opt==1){
			int x,c;scanf ("%d%d",&x,&c);
//			fprintf (stderr,"%d %d\n",x,c);
			//Case1
			if (vis(c,x)) continue;ins(c,x);
			//Case2
			int l=dfn[x],r=dfn[x]+size[x]-1;
			set < pair<int,int> > :: iterator it;
			while (1){
				it=S[c].lower_bound(make_pair(l,r));
				if (it==S[c].end()) break;
				if ((*it).first>r) break;
				int u=to[(*it).first];
				T.update(1,1,n,dfn[u],dfn[u]+size[u]-1,-1);
				S[c].erase(it);
			}
			S[c].insert(make_pair(l,r));
			T.update(1,1,n,dfn[x],dfn[x]+size[x]-1,1);
		}else{
			int x;scanf ("%d",&x);
			printf ("%lld\n",T.query(1,1,n,dfn[x],dfn[x]+size[x]-1));
		}
	}
	return 0;
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM