zoukankan      html  css  js  c++  java
  • Codeforces Round #699 (Div. 2)

    E.Sorting Books

    题目描述

    点此看题

    解法

    ( t Almostspace art!Thespace artspace ofspace enumeration!)

    不难发现每本书最多移动一次,移动多次一定是不优的。

    那么每本书就有两种状态:不移动和移动。我们枚举每本书的状态,如果以最后一本不动书为界限,那么前面的书如果属于同一种类,那么一定同时移动或者同时不移动,否则这本不动书就会使他们不能相聚。

    所以我们枚举最后一本不动书,它后面的数一定要动,现在要决策它前面的书,其实就是选出来书种类的区间不能相交,那么设 (dp[i]) 表示前 (i) 本书最大的不动书个数,预处理出每种书的左端点 (l[i]),右端点 (r[i]) 和出现次数 (cnt[i]) 就可以简单转移了。

    但是代码中你会发现名与实的分离,去看注释!

    总结

    当没有思路的时候,枚举法会有奇效,虽然我们不用他实现代码,但是枚举有助于思考。

    #include <cstdio>
    #include <vector>
    #include <iostream>
    using namespace std;
    const int M = 500005;
    int read()
    {
    	int x=0,f=1;char c;
    	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
    	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
    	return x*f;
    }
    int n,a[M],l[M],r[M],dp[M],cnt[M];
    vector<pair<int,int>> g[M]; 
    signed main()
    {
    	n=read();
    	for(int i=1;i<=n;i++)
    	{
    		a[i]=read();
    		if(!l[a[i]]) l[a[i]]=i;
    		r[a[i]]=i;
    		cnt[a[i]]++;
    	}
    	for(int i=1;i<=n;i++)
    		if(cnt[i])
    			g[l[i]-1].push_back(make_pair(r[i],cnt[i]));
    	for(int i=0;i<n;i++)
    	{
    		for(auto x:g[i])
    			dp[x.first]=max(dp[x.first],dp[i]+x.second);
    		dp[i+1]=max(dp[i+1],dp[i]);
    	}
    	int ans=n;
    	for(int i=1;i<=n;i++) cnt[i]=0;
    	for(int i=n;i>=1;i--)
    	{
    		cnt[a[i]]++;
    		ans=min(ans,n-dp[i-1]-cnt[a[i]]);
    		//后面的书不应该都动吗?为什么要减cnt[a[i]]
    		//因为dp[i-1]其实没有算清楚
    		//对于a[i]这个种类的书,可能会不动 
    		//但是我们在前面考虑不到其不动的情况(看转移)
    		//所以实际上是考虑前面不动的情况 
    	}
    	printf("%d
    ",ans);
    }
    

    F. AB Tree

    题目描述

    点此看题

    解法

    显然的思路是按深度分层,最理想的状况是每一行的字符都一样,答案是深度。

    如果不能达到这个状态呢?那么构造给出最优解是深度(+1)

    我们按层数从小到大考虑,如果某一层不能填入相同的字符,设出现次数较大的字符为 (c),因为要降低对儿子的影响,所以把非叶节点填入颜色 (c),设 (m) 为未填的字符,因为非叶节点的出现次数 (leqfrac{m}{2})(cgeqfrac{m}{2}),所以一定能填满。然后把 (c) 填入这一层的叶节点,剩下的就只有另一种颜色的,填入到其它点中,不难发现只有当前层会多一种不同的字符。

    现在的问题是判断能不能每行填入相同的字符,这是个背包问题,物品就是每一层的节点数,但是直接做是 (O(n^2)) 的,优化背包需要观察一下题目性质。因为物品的种类数最多是 (sqrt n) 的,而我们求解的是存在性问题,多重背包是可以 (O(1)) 跑的,只要记录这一种物品还能取多少,就可以类似完全背包跑了,时间复杂度 (O(nsqrt n))

    总结

    我总结不动了,构造法的使用总是出其不意。

    #include <cstdio>
    #include <vector>
    #include <cstring>
    #include <iostream>
    using namespace std;
    const int M = 100005;
    int read()
    {
    	int x=0,f=1;char c;
    	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
    	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
    	return x*f;
    }
    int n,m,k,x,y,tot,f[M],d[M],son[M];
    int a[M],b[M],cnt[M],dp[500][M];
    vector<int> v[M];char ans[M];
    struct edge
    {
    	int v,next;
    }e[2*M];
    void dfs(int u,int fa)
    {
    	son[u]=0;
    	d[u]=d[fa]+1;
    	v[d[u]].push_back(u);
    	m=max(m,d[u]);
    	for(int i=f[u];i;i=e[i].next)
    	{
    		int v=e[i].v;
    		if(v==fa) continue;
    		dfs(v,u);son[u]=1;
    	}
    }
    void work(int k,int n)
    {
    	if(!k) return ;
    	int sl=a[b[k]]-dp[k][n];
    	cnt[b[k]]=sl;
    	work(k-1,n-sl*b[k]);
    }
    signed main()
    {
    	n=read();x=read();y=n-x;
    	for(int i=2;i<=n;i++)
    	{
    		int j=read();
    		e[++tot]=edge{i,f[j]},f[j]=tot;
    		e[++tot]=edge{j,f[i]},f[i]=tot;
    	}
    	dfs(1,0);
    	for(int i=1;i<=m;i++)
    		a[v[i].size()]++;
    	memset(dp,-1,sizeof dp);
    	dp[0][0]=0;
    	for(int i=1;i<=n;i++)
    	{
    		if(!a[i]) continue;
    		b[++k]=i;
    		for(int j=0;j<=n;j++)
    		{
    			if(dp[k-1][j]>=0) dp[k][j]=a[i];
    			if(j>=i) dp[k][j]=max(dp[k][j],dp[k][j-i]-1);
    		}
    	}
    	if(dp[k][x]>=0)
    	{
    		work(k,x);
    		for(int i=1;i<=n;i++) ans[i]='b';
    		for(int i=1;i<=m;i++)
    		{
    			int len=v[i].size();
    			if(cnt[len])
    			{
    				cnt[len]--;
    				for(auto x:v[i]) ans[x]='a';
    			}
    		}
    		printf("%d
    ",m);
    		printf("%s
    ",ans+1);
    		return 0;
    	}
    	printf("%d
    ",m+1);
    	for(int i=1;i<=m;i++)
    	{
    		int len=v[i].size();
    		if(len<=x)
    		{
    			for(auto j:v[i]) ans[j]='a';
    			x-=len;
    		}
    		else if(len<=y)
    		{
    			for(auto j:v[i]) ans[j]='b';
    			y-=len;
    		}
    		else if(x>y)
    		{
    			for(auto j:v[i])
    				if(son[j]) ans[j]='a',x--;
    			for(auto j:v[i])
    				if(!ans[j] && x) ans[j]='a',x--;
    			for(auto j:v[i])
    				if(!ans[j]) ans[j]='b';
    		}
    		else
    		{
    			for(auto j:v[i])
    				if(son[j]) ans[j]='b',y--;
    			for(auto j:v[i])
    				if(!ans[j] && y) ans[j]='b',y--;
    			for(auto j:v[i])
    				if(!ans[j]) ans[j]='a';
    		}
    	}
    	printf("%s",ans+1);
    }
    
  • 相关阅读:
    第三章 操作符
    exit函数
    详解C++ friend关键字
    放假了,暂告一段落,迎接研究生
    使用const 提高函数的健壮性
    使用断言assert
    对return 语句的正确性和效率进行检查
    函数堆栈
    somethings about QSplitter
    引用和引用参数
  • 原文地址:https://www.cnblogs.com/C202044zxy/p/14924802.html
Copyright © 2011-2022 走看看