1. 程式人生 > 實用技巧 >2020.11.24清北課堂訓練賽題解報告

2020.11.24清北課堂訓練賽題解報告

難易程度

依舊是一場菜雞受虐賽,盡力了QAQ,今天\(dp\)一大堆,只\(A\)掉了一個題目,其他題目該\(WA\)\(WA\),該\(0\)\(0\)

得分情況

T1 30分

T2 100分

T3 0分

T4 0分

(什麼時候我後倆題可以得點分)

題目分析

T1 \(diyiti\)

題目分析
30pts 爆搜
#include<iostream>
#include<cstdio>
#include<cmath> 
#define int long long 
using namespace std;
const int N=1e5+9;
int f[10000009];
int v[N],w[N];
int vis[N];
int n,k;
int res,ans;
void dfs(int num,int val)
{
	if(num>k)return;
	ans=max(val,ans);
	for(int i=1;i<=n;i++)
	{
		if(!vis[i])
		{
			vis[i]=1;
			dfs((num|w[i]),val+v[i]);
			vis[i]=1;
		}
	}
}
signed main()
{
	cin>>n>>k;
	int va=0,wa=0;
	for(int i=1;i<=n;i++)
	{
		cin>>w[i]>>v[i];
		if(i==1) wa=w[i];
		else wa|=w[i];
		va+=v[i];
	}
	if(wa<=k)
	{
		cout<<va<<endl;
		return 0;
	}
	dfs(0,0);
	cout<<ans<<endl;
	return 0;
}//30pts 
100pts
  • \(k\)小可以分成若干類,列舉第一位不同的地方,接下來就要求的是某一個數的子集
  • 然後能選就選,複雜度 \(O(n \times 30)\)

T2 \(dierti\)

題目分析
  • \(DP\),首先依次列舉序列,設\(f[i]\)為前\(i\)個數產生最長不互斥子序列的元素個數
  • 然後在設定一個量瞅一瞅最大值,簡單記為\(bige_i\)表示記錄\(f[i]\)的最大值。然後我們可以得到狀態轉移方程為

\[f_i=max(f_j+1),j<i \ \ and \ \ a_i \vee a_{i-1} >0 \]

剪枝優化
  • 顯然的是,我們可以看到上面的情況是一種\(O(n^2)\)
    做法,是跑不過去的\(QAQ\),然後我們可以想到用剪枝來減去無用狀態,當我們列舉所有狀態時,在前面幾個狀態中我們已經找到了此時的最優解,所以列舉接下來的所有狀態就沒有任何意義了,那麼我們就可以把前一個的狀態給存下來,只要當前的\(f_i>bige_{i-1}\),那麼就表明我們已經找到了最優解,所以直接跳出迴圈找下一個就可以了
100pts程式碼
#include<cstdio>
#include<queue>
#include<stack>
#include<map>
#include<algorithm>
#include<iostream>
#include<cmath>
using namespace std;
const int N=2e5+9;
int f[N],bige[N];//dp轉移方程,最大值 
int num[N]; 
int main()
{
	int n;
	cin>>n;
	for(int i=1;i<=n;++i)
		scanf("%d",&num[i]);
	for(int i=1;i<=n;++i)
		f[i]=1;
	for(int i=1;i<=n;++i)
	{
		for(int j=i-1;j;--j)
		{
			if(bige[i-1]<f[i]) break;
			//說明已經找到了,因為前一個位置的最大值肯定小於後一個位置已經找到的狀態
			//減少無用狀態 
			if(num[i]&num[j])
			f[i]=max(f[j]+1,f[i]);
			//如果這兩個可以湊一塊,看一看加上會不會更優 
		}
		bige[i]=max(bige[i-1],f[i]);//記錄一下最大值 
	}
	cout<<f[n]<<endl;
	return 0;
}//100pts

T3 \(dierti\)

題目分析
  • 考慮\(dp\) 可以先考慮方案數而不是權值和以方便思考,實際上兩者轉移是類似的
  • \(f(i,l,r)\)表示只考慮第\((l,r)\)這些行,第\(i\)列以後的這部分的方案數,對於一個固定的\(i,l,r\),顯然可以再\(dp\)輔助計算,\(dp(k,c)\)表示考慮完前\(k\)行,當前這一位最大填了\(c\),每一次轉移加一段\(c+1\),利用\(f(i+1)\)的資訊來轉移。
  • 最暴力的做法,複雜度為\(O(10mn^4)\)
剪枝優化
  • 考慮優化,很顯然的對於每一個 \(l\),不同的\(r\)的內層\(dp\)幾乎都是一樣的,放一起做就好了,本質就是內層的\(dp\)只需要先美劇\(i,l\)即可計算,複雜都為\(O(10mn^3)\)
    最後注意轉移的時候複雜度不要寫殘了。權值和這個也一樣,再設一個\(g(i,l,r)\)轉移類似同上
程式碼實現(打不出來,超出理解範圍了\(QAQ\)

T4 \(disiti\)

題目分析(快饒了我吧,\(QAQ\),腦袋快離開自己了)
  • 首先我們這個分裂的過程複雜,不方便考慮,所以我們就考慮每一個石子,而不是每一堆石子,一次操作相當於給每個石子分配一個\(01\)標號,那麼滿足每次標記\(1\)的石子不超過\(m\)個,這樣我們每個石子對應著一個等長的\(01\)串,我們的目標就是要讓這個\(01\)串兩兩不同。
  • 我們可以二分答案,考慮如何判定某一個\(mid\),是否合法,一個的必要條件是\(mid\times m\)要足夠大,即存在一組方案使得\(01\)串兩兩不同且\(1\)的個數\(≤ mid\times m\),事實上這個也是充分的,我們給出構造來說明這一點,首先一個貪心是按照\(1\)的個數從小到大放的,顯然\(1\)的個數不是最大的那些串的\(1\)並不是平均分配的,那麼問題轉化成了要取若干個\([1,n]\)\(m\)元子集,使得每個數出現的次數的極差\(≤1\)。我們隨便構造一組解,如果不合法那麼久找出一個出現次數太大的位置\(i\),一個出現次數太小的位置\(j\),顯然存在一個\(01\)串滿足\(s[i]=1,s[j]=0\),並且不存另一個\(01\)\(t\)滿足\(t\oplus s=2^i\oplus 2^j\)。因此我們只需要\(swap(s[i],s[j])\).迭代顯然可以優先次結束。
  • 具體實現需要求組合數,這裡不能取模但是由於\(n≤10^9\)所以直接寫不會爆$long long $.組合數增長是很快的所以暴力的複雜度可以接受
程式碼實現 (猝)

賽後總結

今天感覺比賽已經進入了自己達不到的領域,前兩個題推起來也吃力,後面的題幾乎沒有思路,連暴力也打不出來,不過從這次考試中可以很好的看出動態規劃的妙用,對自己而言,寧願寫動規也不願意打一個搞人心態的大暴力,每一次考試都可以看出自己薄弱的地方,這次反應出來的是\(dp\)和二進位制不熟練

知識點彙總

  • T1 二進位制
  • T2 位運算,\(DP\)
  • T3 困難區間\(DP\)
  • T4 組合數學,二分答案,腦子;

悲慘完結QAQ