省选题目选做(2)

省选题目选做(2)

\(T1\)信号传递

算是把每一档分都得了一遍...

第一步\(O(n\times2^m)\)比较好想,直接统计前后贡献即可

按照一开始转移方式每个点单独转移

//可以提前对于每个点要传递出去的点连边
//然后转移的时候,枚举这一步要新增哪个点
//貌似不太能处理位置
//考虑怎么能够每次转移一个点求贡献,不需要知道前面的位置
//考虑结论:
//x<y (res+=y-x)
//x>y (res+=kx+ky)
//那么这样的话,考虑我们每个点记录一下贡献
//当前点连出的点有num1没有放置的,就-num*x
//当前点连出的点有num2已经放置的,就+num*k*x
//当前点连入的点有num3没有放置的,那么就+num*k*x
//当前点连入的点有num4已经放置的,那么就+num*x
//复杂度2^m*n?
#define Eternal_Battle ZXK
#include<bits/stdc++.h>
#define MAXN 25
using namespace std;
int dp[1<<23],S[MAXN],n,m,k;
vector<int>rd[MAXN],fx[MAXN];
int lowbit(int x)
{ 
    return x&(-x);
}
int Count(int x)
{
	int res=0;
	while(x)
	{
		  x-=lowbit(x);
		  res++;
	}
	return res;
}
void Make(int x)
{
	 for(int i=0;i<n;i++)
	 {
	 	 cout<<((x>>i)&1);
	 }
	 cout<<" ";
}
int main()
{
	scanf("%d%d%d",&n,&m,&k);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&S[i]);
	}
	for(int i=1;i<n;i++)
	{
        if(S[i]==S[i+1]) continue;
	    rd[S[i]].push_back(S[i+1]);
	    fx[S[i+1]].push_back(S[i]);
	}
	memset(dp,0x3f,sizeof(dp));
	dp[0]=0;
	for(int i=0;i<=(1<<m)-1;i++)
	{
		int x=Count(i)+1;
		for(int j=1;j<=m;j++)
		{
			if(((i>>(j-1))&1)==0)
			{
				 int num1=0,num2=0,num3=0,num4=0;
				 for(int k=0;k<rd[j].size();k++)
				 {
				 	 int y=rd[j][k];
				 	 if(((i>>(y-1))&1)==0) num1++;
				 	 else num2++;
				 }
				 for(int k=0;k<fx[j].size();k++)
				 {
				 	 int y=fx[j][k];
				 	 if(((i>>(y-1))&1)==0) num3++;
				 	 else num4++;
				 }
				 dp[i|(1<<(j-1))]=min(dp[i|(1<<(j-1))],dp[i]-num1*x+num2*k*x+num3*k*x+num4*x);
//				 Make(i|(1<<(j-1)));
			}
		}
	}
	cout<<dp[(1<<m)-1];
}

那么比较容易优化到不枚举边,而选择枚举点,并且预处理贡献\(O(m^22^m)\)

//可以提前对于每个点要传递出去的点连边
//然后转移的时候,枚举这一步要新增哪个点
//貌似不太能处理位置
//考虑怎么能够每次转移一个点求贡献,不需要知道前面的位置
//考虑结论:
//x<y (res+=y-x)
//x>y (res+=kx+ky)
//那么这样的话,考虑我们每个点记录一下贡献
//当前点连出的点有num1没有放置的,就-num*x
//当前点连出的点有num2已经放置的,就+num*k*x
//当前点连入的点有num3没有放置的,那么就+num*k*x
//当前点连入的点有num4已经放置的,那么就+num*x
//复杂度2^m*n?
#define Eternal_Battle ZXK
#include<bits/stdc++.h>
#define MAXN 25
using namespace std;
int rd[MAXN][MAXN],fx[MAXN][MAXN];
int dp[1<<23],S[MAXN],n,m,k;
int zy[24][1<<23];
int lowbit(int x)
{ 
    return x&(-x);
}
int Count(int x)
{
	int res=0;
	while(x)
	{
		  x-=lowbit(x);
		  res++;
	}
	return res;
}
void Make(int x)
{
	 for(int i=0;i<n;i++)
	 {
	 	 cout<<((x>>i)&1);
	 }
	 cout<<" ";
}
int main()
{
//	freopen("dp1.in","r",stdin);
//	freopen("dp1.in","r",stdin);
	scanf("%d%d%d",&n,&m,&k);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&S[i]);
	}
	for(int i=1;i<n;i++)
	{
        if(S[i]==S[i+1]) continue;
        rd[S[i]][S[i+1]]++;
        fx[S[i+1]][S[i]]++;
	}
	memset(dp,0x3f,sizeof(dp));
	dp[0]=0;
	for(int j=1;j<=m;j++)
	{
//		cout<<"now: "<<j<<"\n";
		for(int i=0;i<=(1<<m)-1;i++)
		{
			if(((i>>(j-1))&1)!=0) continue; 
		    int x=Count(i)+1;
			int num1=0,num2=0,num3=0,num4=0;
			for(int k=1;k<=m;k++)
			{
				if(((i>>(k-1))&1)==0) num1+=rd[j][k],num3+=fx[j][k];
				else num2+=rd[j][k],num4+=fx[j][k];;
			}
//			for(int k=0;k<rd[j].size();k++)
//			{
//			 	int y=rd[j][k];
//			 	if(((i>>(y-1))&1)==0) num1++;
//			 	else num2++;
//			}
//			for(int k=0;k<fx[j].size();k++)
//			{
//			 	int y=fx[j][k];
//			 	if(((i>>(y-1))&1)==0) num3++;
//			    else num4++;
//			}
			zy[j][i]=-num1*x+num2*k*x+num3*k*x+num4*x;
		}
	}
//	return 0;
	for(int i=0;i<=(1<<m)-1;i++)
	{
//		int x=Count(i)+1;
		for(int j=1;j<=m;j++)
		{
			if(((i>>(j-1))&1)==0)
			{
//				 int num1=0,num2=0,num3=0,num4=0;
//				 for(int k=0;k<rd[j].size();k++)
//				 {
//				 	 int y=rd[j][k];
//				 	 if(((i>>(y-1))&1)==0) num1++;
//				 	 else num2++;
//				 }
//				 for(int k=0;k<fx[j].size();k++)
//				 {
//				 	 int y=fx[j][k];
//				 	 if(((i>>(y-1))&1)==0) num3++;
//				 	 else num4++;
//				 }
//                 cout<<zy[j][i]<<" "<<-num1*x+num2*k*x+num3*k*x+num4*x<<"\n";
				 dp[i|(1<<(j-1))]=min(dp[i|(1<<(j-1))],dp[i]+zy[j][i]);
			}
		}
	}
	cout<<dp[(1<<m)-1];
}
//21467158

第三部考虑预处理的过程也是可以递推的

每次只需要新增多出来的贡献就好了

大概就是一开始假设全在后面,然后一个个往前移动,处理一下,复杂度\(O(m2^m)\)

按照原来看到的技巧,本位不放数字,左右中间移动可优化空间复杂度

//可以提前对于每个点要传递出去的点连边
//然后转移的时候,枚举这一步要新增哪个点
//貌似不太能处理位置
//考虑怎么能够每次转移一个点求贡献,不需要知道前面的位置
//考虑结论:
//x<y (res+=y-x)
//x>y (res+=kx+ky)
//那么这样的话,考虑我们每个点记录一下贡献
//当前点连出的点有num1没有放置的,就-num*x
//当前点连出的点有num2已经放置的,就+num*k*x
//当前点连入的点有num3没有放置的,那么就+num*k*x
//当前点连入的点有num4已经放置的,那么就+num*x
//复杂度2^m*n?行吧,我已经快吐了 
//30->70->100,极致优化过程呗 
#define Eternal_Battle ZXK
#include<bits/stdc++.h>
#define MAXN 25
using namespace std;
int dp[1<<23],id[1<<23],S[MAXN],n,m,k;
int cnt[MAXN][MAXN];
int zy[24][1<<23];
int lowbit(int x)
{ 
    return x&(-x);
}
int Count(int x)
{
	int res=0;
	while(x)
	{
		  x-=lowbit(x);
		  res++;
	}
	return res;
}
void Make(int x)
{
	 for(int i=0;i<n;i++)
	 {
	 	 cout<<((x>>i)&1);
	 }
	 cout<<" ";
}
int main()
{
//	freopen("dp1.in","r",stdin);
//	freopen("dp1.in","r",stdin);
	scanf("%d%d%d",&n,&m,&k);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&S[i]);
	}
	for(int i=1;i<n;i++)
	{
        if(S[i]==S[i+1]) continue;
        cnt[S[i]][S[i+1]]++;
	}
    for(int i=1;i<=m;i++)
    {
    	for(int j=1;j<=m;j++)
    	{
    		if(j==i) continue;
    		zy[i][0]+=-cnt[i][j];
    		zy[i][0]+=cnt[j][i]*k;
		}
	}
	for(int i=0;i<=m;i++)
	{
		id[1<<i]=i+1;
	}
	for(int i=1;i<=m;i++)
	{
		for(int j=1;j<(1<<(m-1));j++)
		{
			int val=lowbit(j),poz=id[val];
			if(poz>=i) poz++;
			zy[i][j]=zy[i][j^val]+cnt[poz][i]+cnt[i][poz]*k+cnt[i][poz]-cnt[poz][i]*k;
//			cout<<"zy: "<<i<<" "<<j<<" "<<zy[i][j]<<"\n";
		}
	}
	memset(dp,0x3f,sizeof(dp));
	dp[0]=0;
//	for(int j=1;j<=m;j++)
//	{
////		cout<<"now: "<<j<<"\n";
//		for(int i=0;i<=(1<<m)-1;i++)
//		{
//			if(((i>>(j-1))&1)!=0) continue; 
//		    int x=Count(i)+1;
//			int num1=0,num2=0,num3=0,num4=0;
//			for(int k=1;k<=m;k++)
//			{
//				if(((i>>(k-1))&1)==0) num1+=rd[j][k],num3+=fx[j][k];
//				else num2+=rd[j][k],num4+=fx[j][k];
//			}
//			zy[j][i]=-num1*x+num2*k*x+num3*k*x+num4*x;
//		}
//	}
//	return 0;
	for(int i=0;i<=(1<<m)-1;i++)
	{
		int x=Count(i)+1;
//		cout<<i<<" "<<dp[i]<<"\n";
		for(int j=1;j<=m;j++)
		{
			if((i>>(j-1)&1)==0)
			{
//				 int num1=0,num2=0,num3=0,num4=0;
//				 for(int k=0;k<rd[j].size();k++)
//				 {
//				 	 int y=rd[j][k];
//				 	 if(((i>>(y-1))&1)==0) num1++;
//				 	 else num2++;
//				 }
//				 for(int k=0;k<fx[j].size();k++)
//				 {
//				 	 int y=fx[j][k];
//				 	 if(((i>>(y-1))&1)==0) num3++;
//				 	 else num4++;
//				 }
//                 cout<<zy[j][i]<<" "<<-num1*x+num2*k*x+num3*k*x+num4*x<<"\n";
                 int pS=i%(1<<j-1); 
				 dp[i|(1<<(j-1))]=min(dp[i|(1<<(j-1))],dp[i]+x*zy[j][(i-pS)/2+pS]); 
			}
		}
	}
	cout<<dp[(1<<m)-1];
}
//21467158

\(T2\)春节十二响

询问最小的分配方案使得每一部分的内存和最小

如果我想的话,贪心是必然的,那么最大的肯定会选,那么从大往小枚举,被迫新开的时候再开就好了,必然是最优的

考虑证明,假设我们存在一种方案,使得目前最大即使能放入一个仍重开一个会更优

那么唯一可能的情况就是后面的多一部分进入前面的这个,需要满足的条件是\(zx[new]=now,zx[new]!=ls,zx[now]!=ls\)其实发现这种情况,即使\(x\)放进去,\(new\)依旧新开,那么\(x\)放进去显然更优

到这里期望得分\(45pts\)

还是考虑我们合并的过程,同一个子树内,我们可以分成若干个堆目前

那么我们合并两个子树,比较优的是,让两个大的互相伤害,然后放入目前节点堆里即可

至于合并的话,启发式合并可以做到\(O(nlogn)\)

然后实际代码很好写了

#define Eternal_Battle ZXK
#include<bits/stdc++.h>
#define int long long
#define MAXN 200005
using namespace std;
int head[MAXN],nxt[MAXN],to[MAXN],tot;
priority_queue<int>q[MAXN];
int Me[MAXN],n;
void add(int u,int v)
{
	 tot++;
	 to[tot]=v;
	 nxt[tot]=head[u];
	 head[u]=tot;
}
void Merge(int x,int y)
{
	 if(q[x].size()<q[y].size()) swap(q[x],q[y]);
     priority_queue<int>Mid;
	 while(q[y].size())
     {
     	   Mid.push(max(q[x].top(),q[y].top()));
     	   q[x].pop();
     	   q[y].pop();
	 }
     while(Mid.size())
	 {
	 	  q[x].push(Mid.top());
	 	  Mid.pop();
	 }
}
void dfs(int now)
{
     for(int i=head[now];i;i=nxt[i])
	 {
	     int y=to[i];
         dfs(y);
         Merge(now,y);
	 }
	 q[now].push(Me[now]);
}
signed main()
{
	scanf("%lld",&n);
	for(int i=1;i<=n;i++)
	{
		scanf("%lld",&Me[i]);
	}
	for(int i=2,fa;i<=n;i++)
	{
		scanf("%lld",&fa);
		add(fa,i);
	}
	dfs(1);
	int Ans=0;
	while(q[1].size())
	{
		  Ans+=q[1].top();
		  q[1].pop();
	}
	cout<<Ans<<"\n";
}
posted @ 2022-04-05 22:13  Authentic_k  阅读(22)  评论(0编辑  收藏  举报