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;
    }
    
  • 相关阅读:
    Nim or not Nim? hdu3032 SG值打表找规律
    Maximum 贪心
    The Super Powers
    LCM Cardinality 暴力
    Longge's problem poj2480 欧拉函数,gcd
    GCD hdu2588
    Perfect Pth Powers poj1730
    6656 Watching the Kangaroo
    yield 小用
    wpf DropDownButton 源码
  • 原文地址:https://www.cnblogs.com/crazyzh/p/12070114.html
Copyright © 2011-2022 走看看