ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

高考集训讲课(To 高一)

2022-06-09 12:33:35  阅读:146  来源: 互联网

标签:int 讲课 ++ 枚举 MAXN 高一 集训 dp define


高考集训讲课(To 高一)

主要是怕下午讲着讲着把自己讲懵了,有一定的迷糊概率

经过机房的讨论,一致认为插头\(DP\)实用性不大,所以这次不讲了,先把重要的讲一讲。

顺便吐槽一下,凭什么另外几个人都是几个相互联系的知识点,到我这跨越这么大。。。

反正都是\(trick\)直接上题,没有知识点讲


状压\(DP\)

\(P7519\)滚榜

这是上次想讲的题

费用提前\(+\)状压\(DP\)

我们最后只求情况数,那么中间每个队伍的通过题数是无需关注的

从最开始考虑,我们暴力枚举每种情况,如何判断这种情况能否满足

我们有序列\(a_1,a_2...a_n\),我们的要求是,每次\(a_{i-1}+b_{i-1}<a_i+b_i\),并且\(b_i>b_i-1\),只需要贪心的扫一遍维护一下即可,保证每次确定的\(b_i\)尽可能小

考虑状压,\(dp[i][j][k]\)表示我们已经确定\(i\)状态的队伍分数,最后一个确定的是\(j\),我们已经使用的题数是\(j\)

由于我们上一个都比前一个多,我们直接把后面的都加上一个这个数的值就好了(费用提前)

转移易得

//有个东西叫费用提前
//当你枚举这个选了多少个的时候,为了保证后面的也是
//那么后面的就统一加上,然后那么这个时候差不变
//该加多少还是加多少
#include<bits/stdc++.h>
#define int long long
using namespace std;
int dp[1<<13][15][505],a[15],Ans,n,m;
int lowbit(int x)
{
	return x&(-x);
}
int Count(int num)
{
	 int res=0;
	 while(num)
	 {
	 	   res++;
	 	   num-=lowbit(num);
	 }
	 return res;
}
signed main()
{
	cin>>n>>m;
	int Max=0;
	a[0]=-1;
	for(int i=1;i<=n;i++)
	{
		cin>>a[i];
	    if(a[i]>a[Max])
	    {
	    	Max=i;
		}
	}
	for(int i=1;i<=n;i++)
	{
		int tar=n*(a[Max]-a[i]+(Max<i));
		if(tar<=m)
		{
		   dp[1<<(i-1)][i][tar]=1;
		}
	}
	for(int i=1;i<=(1<<n)-1;i++)
	{
		int Num=Count(i);
		for(int j=1;j<=n;j++)
		{
			if((i>>(j-1))&1)
			{
			    for(int k=0;k<=m;k++)
			    {
			    	for(int z=1;z<=n;z++)
			    	{
			    		if(!((i>>(z-1))&1))
			    		{
			    			 int tar=k+(n-Num)*max(0ll,a[j]-a[z]+(j<z));
			    			 if(tar<=m)
			    			 dp[i|(1<<(z-1))][z][tar]+=dp[i][j][k];
						}
					}
				}
			}
		}
	}
	for(int i=1;i<=n;i++)
	{
		for(int j=0;j<=m;j++)
		{
			Ans+=dp[(1<<n)-1][i][j];
		}
	}
	cout<<Ans<<endl;
}

\(P6622\)信号传递

比较套路的贡献分开计算

设我们目前放的点是\(x\)

\(x<y,res+=(y-x)\)

\(x>y,res+=(kx+ky)\)

那么我们可以处理每个位置时候计算一下贡献就好了

可以参考一下注释

如果直接枚举边\(30pts\),提前统计一下的话是\(70pts\)

\(30pts:\)

//可以提前对于每个点要传递出去的点连边
//然后转移的时候,枚举这一步要新增哪个点
//貌似不太能处理位置
//考虑怎么能够每次转移一个点求贡献,不需要知道前面的位置
//考虑结论:
//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];
}

\(70pts\)

考虑预处理转移\(zy[j][i]\)

表示\(j\)点在\(i\)状态转移会有多少代价

#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];;
			}
			zy[j][i]=-num1*x+num2*k*x+num3*k*x+num4*x;
		}
	}
	for(int i=0;i<=(1<<m)-1;i++)
	{
		for(int j=1;j<=m;j++)
		{
			if(((i>>(j-1))&1)==0)
			{
				 dp[i|(1<<(j-1))]=min(dp[i|(1<<(j-1))],dp[i]+zy[j][i]);
			}
		}
	}
	cout<<dp[(1<<m)-1];
}

我们发现预处理复杂度有点高,那就考虑递推预处理

由于我们被卡空间,考虑把中间没用的那一位抹去左右拼接即可

#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;

		}
	}
	memset(dp,0x3f,sizeof(dp));
	dp[0]=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 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

计数

常见的话有\(DP\)计数,生成函数计数(后面这个和多项式有关,就交给\(HH\)力,我就不管了)

\(CF724F\ Uniformly\ Branched\ Trees\)

无论是什么计数,最开始的出发点就是找特征点

树上计数,每棵树最特殊的位置就是重心,因为重心只有\(1/2\)个

考虑组合问题,在\(x\)方案里面选\(t\)个,可重复

答案是\(\binom{x+t-1}{t}\)

感性想一想就是,我们每次选一个,在后面加一个方案表示,下一次可以选和这个一样的,然后我们相当于加了\(t-1\)个方案,一共在这些里面选\(t\)个

\(dp[i][j][k]\)表示节点数为\(i\),有\(j\)棵子树,子树大小都不超过\(k\),并且根是重心的树的数目

首先考虑单重心的情况\(n\%2=1\)

我们转移的时候,最外层枚举节点数\(i\),内层枚举子树\(j\),再枚举不超过节点数\(k\),再枚举节点\(k\)

转移易得

\(dp[i][j][k]+=dp[i-t\times k][j-t][k-1]\times C(dp[k][d-1][k-1]+t-1,t)\)

然后双中心会算重,减去一部分就好了

\(Ans-=C(dp[n/2][d-1][n/2-1],2)\)

#include<bits/stdc++.h>
#define int long long
#define MAXN 1005
using namespace std;
int fac[MAXN+5],inv[MAXN+5];
int dp[MAXN][20][MAXN],n,d,mod;
void Init()
{
	 fac[0]=inv[0]=1;
	 fac[1]=inv[1]=1;
	 for(int i=2;i<=MAXN;i++)
	 {
	 	 fac[i]=fac[i-1]*i%mod;
	 	 inv[i]=(mod-mod/i)*inv[mod%i]%mod;
	 }
	 for(int i=1;i<=MAXN;i++)
	 {
	 	 inv[i]=(inv[i-1]*inv[i])%mod;
	 }
}
int C(int n,int m)
{
	if(n<m) return 0; 
	int res=1;
	for(int i=1;i<=m;i++) res=(res*(n-i+1))%mod;
	return res*inv[m]%mod;
	return fac[n]*inv[m]%mod*inv[n-m]%mod;
}
signed main()
{
	scanf("%lld%lld%lld",&n,&d,&mod);
	Init();
	if(n<=2)
	{
	   cout<<1;
	   return 0;
	}
	for(int i=0;i<=n;i++) dp[1][0][i]=1;//节点数1,0子树,子树大小不超过i
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=min(i-1,d);j++)
		{
			for(int k=1;k<=n;k++)
			{
				dp[i][j][k]=dp[i][j][k-1];
				for(int t=1;i>t*k&&j-t>=0;t++)
				{
					if(k!=1) dp[i][j][k]=(dp[i][j][k]+dp[i-t*k][j-t][k-1]*C(dp[k][d-1][k-1]+t-1,t)%mod)%mod;
					else dp[i][j][k]=(dp[i][j][k]+dp[i-t*k][j-t][k-1]*C(dp[k][0][k-1]+t-1,t)%mod)%mod;
				}
			}
		}
	}
	if(n%2) cout<<dp[n][d][n/2]<<"\n";
	else cout<<(dp[n][d][n/2]+mod-C(dp[n/2][d-1][n/2-1],2))%mod;
}

\(P8329[ZJOI2022]\)树

这是一道练习题


树形\(dp\),换根\(dp\)

\(P4323 [JSOI2016]\)独特的树叶

考场上想出正解,忘加\(ull\)丢掉\(40pts\)的一道题,我和题解

考虑暴力枚举哪个点是多余的,把剩下的部分树\(hash\)一下,判断是否一样即可,复杂度\(O(n^2)\)

考虑我们对第一棵树以每个根都\(hash\)一次,换根\(dp\)转移一下

第二棵树同样,遇到叶子时候查一下有没有这个树就好了

#include<bits/stdc++.h>
#define ull unsigned long long
#define MAXN 200005
using namespace std;
ull hasha[MAXN],hash1[MAXN],Mid[MAXN];
int head[MAXN],nxt[MAXN],to[MAXN],Ans=MAXN,tot;
int pri[MAXN],siz[MAXN],du[MAXN],cnt;
vector<int>rd[MAXN];
bool vis[MAXN*10];
map<ull,bool>mp;
void add(int u,int v)
{
	 tot++;
	 to[tot]=v;
	 nxt[tot]=head[u];
	 head[u]=tot;
}
void Init()
{
	 for(int i=2;i<=MAXN*10-5;i++)
	 {
	 	 if(!vis[i])
         {
         	pri[++cnt]=i;
         }
         for(int j=1;j<=cnt&&i*pri[j]<=MAXN*10-5;j++)
         {
         	vis[i*pri[j]]=1;
         	if(i%pri[j]==0) break;
         }
 	 }
}
void dfs(int now,int fa)
{
	 siz[now]=1;
	 hasha[now]=1ull;
	 for(int i=head[now];i;i=nxt[i])
	 {
	 	 int y=to[i];
	 	 if(y==fa) continue;
	 	 dfs(y,now);
	 	 siz[now]+=siz[y];
	 	 hasha[now]+=hasha[y]*pri[siz[y]];
	 }
}
void dfs_gen(int now,int fa)
{
	 int now_siz=siz[now];
	 ull now_hash=hasha[now];
	 for(int i=head[now];i;i=nxt[i])
	 {
	 	 int y=to[i];
		 if(y==fa) continue;
         hasha[now]-=(1ull*hasha[y]*pri[siz[y]]);
		 siz[now]-=siz[y];
         siz[y]+=siz[now];
         hasha[y]+=hasha[now]*pri[siz[now]];
         Mid[y]=hasha[y];
         mp[hasha[y]]=true;
         dfs_gen(y,now);
         siz[now]=now_siz;
         hasha[now]=now_hash;
	 }
}
void dfs_b(int now,int fa)
{
	 siz[now]=1;
	 hash1[now]=1ull;
	 for(int i=0;i<rd[now].size();i++)
	 {
	 	 int y=rd[now][i];
	 	 if(y==fa) continue;
	 	 dfs_b(y,now);
	 	 siz[now]+=siz[y];
	 	 hash1[now]+=hash1[y]*pri[siz[y]];
	 }
}
void dfs_genb(int now,int fa)
{
	 int now_siz=siz[now];
	 ull now_hash=hash1[now];
	 for(int i=0;i<rd[now].size();i++)
	 {
	 	 int y=rd[now][i];
	 	 if(y==fa) continue;
	 	 if(du[y]==1)
	 	 {
	 	 	if(mp[hash1[now]-pri[1]])
	 	 	{
	 	 	   Ans=min(Ans,y);
	 	 	}
	 	 }
	 }
	 for(int i=0;i<rd[now].size();i++)
	 {
	 	 int y=rd[now][i];
	 	 if(y==fa) continue;
         hash1[now]-=(1ull*hash1[y]*pri[siz[y]]);
		 siz[now]-=siz[y];
         siz[y]+=siz[now];
//         cout<<"y: "<<y<<" "<<hash1[y]<<" ";
         hash1[y]+=hash1[now]*pri[siz[now]];
//         cout<<hash1[y]<<endl;
         dfs_genb(y,now);
		 if(du[now]==1&&now==1)
         {
         	if(mp[hash1[y]-pri[1]])
         	{
         		Ans=min(Ans,now);
         	}
         }
         siz[now]=now_siz;
         hash1[now]=now_hash;
	 }
}
int n,u,v;
int main()
{
//	freopen("a.in","r",stdin);
//	freopen("a.out","w",stdout);
	scanf("%d",&n);
	Init();
	for(int i=1;i<n;i++)
	{
		scanf("%d%d",&u,&v);
		add(u,v);add(v,u);
	}
    dfs(1,1);
    Mid[1]=hasha[1];
    dfs_gen(1,1);
//    for(int i=1;i<=n;i++)
//    {
//    	cout<<"rt: "<<i<<" "<<Mid[i]<<endl;
//    }
    n++;
    for(int i=1;i<n;i++)
    {
    	scanf("%d%d",&u,&v);
    	rd[u].push_back(v);
    	rd[v].push_back(u);
    	du[u]++;
    	du[v]++;
    }
    dfs_b(1,1);
    dfs_genb(1,1);
    cout<<Ans<<endl;
}


\(wqs\)二分

这东西挺重要的

大部分问题就是取恰好\(k\)个的问题

\(Link\)

使用的前提是答案函数呈凸性

考虑选\(i\)个最优代价是\(f(i)\),我们通过二分额外代价\(k\),让多选一次的代价\(+k\),那么我们最后选\(i\)个代价就是\(f(i)-k\times i\),我们关于这个找到一个最大点,保证了是选这些的最大值,因为得到的点截距最大

\(P2619\) [国家集训队]\(Tree\ I\)

模板题,直接二分\(k\)跑\(kruskal\)

#include<bits/stdc++.h>
#define INF 100000005
#define MAXN 500005
using namespace std;
inline int re()
{
    int x = 0, p = 1;
    char ch = getchar();
    while(ch > '9' || ch < '0') {if(ch == '-') p = -1; ch = getchar();}
    while(ch <= '9' and ch >= '0') {x = (x << 3) + (x << 1) + (ch ^ 48); ch = getchar();}
    return x * p;
}
struct node
{
	   int u,v,w,col;
}rd[MAXN];
int n,m,num,Ans,fa[MAXN];
bool cmp(node a,node b)
{
	 if(a.w==b.w) return a.col<b.col;
	 return a.w<b.w;
}
int Find(int x)
{
	if(fa[x]==x) return x;
	return fa[x]=Find(fa[x]);
}
int check(int mid)
{
	 for(int i=1;i<=m;i++)
	 {
	 	 if(rd[i].col==0) rd[i].w+=mid;
	 }
	 sort(rd+1,rd+1+m,cmp);
	 int cnt=0,Sum=0;
	 for(int i=0;i<n;i++) fa[i]=i;
	 for(int i=1;i<=m;i++)
	 {
	 	 int fu=Find(rd[i].u);
	 	 int fv=Find(rd[i].v);
	 	 if(fu==fv) continue;
	 	 fa[fu]=fv;
	     cnt+=(rd[i].col==0?1:0);
	     Sum+=rd[i].w;
	 }
	 for(int i=1;i<=m;i++)
	 {
	 	 if(rd[i].col==0) rd[i].w-=mid;
	 }
     if(cnt>=num)
     {
     	Ans=Sum-num*mid;
     	return 0;
     }
     return -1;
}
int main()
{
    scanf("%d%d%d",&n,&m,&num);
	for(int i=1;i<=m;i++)
	{
		rd[i].u=re();
		rd[i].v=re();
		rd[i].w=re();
		rd[i].col=re();
	}	
	int l=-111,r=111,res,mid;
	while(l<=r)
	{
		  mid=(l+r)>>1;
		  res=check(mid);
		  if(res==0)
		  {
		  	 l=mid+1;
		  }
		  else r=mid-1;
	}
	cout<<Ans<<"\n";
}

\(P4983\)忘情

式子很好推。

\(Link\)

说人话就是:分\(m\)段,每段价值为\((1+\sum_{i=1}^na_i)^2\)

\(emm\),突然想到,这道题需要斜率优化,貌似还没讲。。。先咕了。


同余最短路

我在苏州集训学到的神奇东西,所以说是我带到机房的

题都比较简单,就口胡吧

\(P2371\)[国家集训队]墨墨的等式

\(P3403\)跳楼机

标签:int,讲课,++,枚举,MAXN,高一,集训,dp,define
来源: https://www.cnblogs.com/Eternal-Battle/p/16358866.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有