1. 程式人生 > 其它 >省選題目選做(2)

省選題目選做(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";
}