zoukankan      html  css  js  c++  java
  • USACO2019 DEC 部分题解

    因为一些翻译问题铂金只过了 B 。之后看只剩余大约 1h 于是弃疗了。

    但是某沙雕同学提供的假翻译似乎也挺能做而且还算有趣,以后可能会把它出出来?也算不错了,膜拜带翻译家

    不过并不能改变最后排名垫底了。

    后来想想感觉 A 还是很能做的

    C 是真的不会,考后找某集训队学长请教,结果只学会了部分分的做法。。。

    以上是废话

    Bronze

    三个普及组题。按题意模拟即可

    代码懒得放了。。。

    Silver

    A

    求第 (k) 个既不被 (3) 整除,也不被 (5) 整除的数字

    (kle 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}) 的时候发生了多少次碰撞。

    (Lle 10^9,nle 5 imes 10^4,w_ile 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)

    (nle 10^5,qle 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}) 最大,输出这个值

    (nle 10^3,mle 10^3,f_ile 10^3)

    没想太多。。看到流量很小,可以枚举流量,然后在原图上跑 dijkstra,复杂度 (O(fmlog 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)

    (nle 10^5,mle 10^5)

    比赛时写了个很蠢的 (nlog^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 imes M) 的矩阵 (a),表示可以消耗 (a_{i,j}) 的代价把字符 (i) 改成字符 (j),求把原字符串改成每个连续段长度都不小于 (K) 的最小代价。

    (n,Kle 10^5,Mle 26)

    首先直接用原矩阵的交换方法不一定最优,拿到手先跑一遍 floyd。

    然后考虑 dp ,(f_i) 表示前 (i) 个中每个连续段都 (ge k) 的最小代价,显然有转移

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

    其中 (cost(i,j,c)) 表示把 ([i,j]) 全都改成字符 (c) 的最小代价,这个可以前缀和预处理。

    观察转移,发现可以在枚举到 (i) 时再把 (i-k) 的决策加进去,这样维护一个前缀 (min) 即可做到 (O(1)) 转移。

    复杂度 (O(n imes 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) 的子树内所有点各有多少种不同的属性,需要输出属性个数之和

    (nle 10^5,qle 10^5)

    dfs 序上子树表示为一段连续的区间,因此两个子树要么包含,要么不相交

    考虑修改一个点,影响的范围是 (x) 的子树,我们给这个范围内所有点的权值都 +1

    但是这样会产生冲突,如果一个属性已经有过了,那么就会有重复,为了消除重复下面分两种情况:

    1. 考虑当前操作的区间被某个区间包含过,那么这是个废区间,直接不做
    2. 如果这个区间包含之前的某些区间,那么撤销那些区间的影响,再加上这个区间

    实现方面,可以用 set 维护情况 2 并支持撤销,操作 1 我写了个很蠢的线段树,现在想想大概是不必要的

    这样,查询操作就可以做一次线段树区间求和

    时间复杂度 (O(qlog 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
    ",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
    ",T.query(1,1,n,dfn[x],dfn[x]+size[x]-1));
    		}
    	}
    	return 0;
    }
    
  • 相关阅读:
    反转链表
    《Java JDK7 学习笔记》课后练习题1
    《Java JDK7 学习笔记》课后练习题2
    《java JDK7 学习笔记》课后练习题3
    SQL与NoSQL(关系型与非关系型)数据库的区别
    编程中编码的来源及发展
    JDK7学习笔记之基础类型
    《java JDK7学习笔记》之跨平台与路径设置
    《java jdk7学习笔记》之java三大平台
    VS2015安装之后加装SQL SERVER2014的步骤
  • 原文地址:https://www.cnblogs.com/crazyzh/p/12070114.html
Copyright © 2011-2022 走看看