模拟51 考试总结

小 丑 就 是 我 自 己

考试经过

据说和好多大佬一起考试,害怕+=inf,说要写文件,担心会爆零。。。
T1一看发现像是dp,但看到数据范围觉得不像,就像直接一个柿子线性球出来,看了半天不会……
T2数学题,很快写出来柿子,一通乱推之下做到了\(O(n)\),又卡了半天常,发现根本不用,淦
T3有想法但是思路比较乱,T4一看NPC?,打了暴力但是没多少分,T1暴力比正解难打就没写,最后10min冲T3线段树+单调栈,无果,嘎
0+100+10+5=115,12个切两个的,菜斩了
发现T1是sbdp,想到昨天刚写了一个类似的博客,人傻了

T1.茅山道术

实际上是一个很水的dp,然而考场上智障没往dp想
发现如果一段区间颜色相同,选了也白选,因此只考虑最近的
\(f_i\)表示考虑前\(i\)个位置,加入一个考虑上一个颜色相同的点在哪里,设其位置为\(j\)
如果\(j\)没有,直接继承\(f_i=f_{i-1}\)
如果有的话要看上一个位置在哪里,要与他相差大于1才有贡献\(f_i=f_{i+1}+[i-j>1]\times f_j\)

#include <bits/stdc++.h>
using namespace std;
const int N=2000050;
const int mod=1e9+7;
int f[N],a[N],last[N],now[N];
signed main()
{		
	freopen("magic.in","r",stdin);
	freopen("magic.out","w",stdout);
	int n;cin>>n;
	for(int i=1;i<=n;i++)scanf("%lld",&a[i]);
	for(int i=1;i<=n;i++)
	{
		last[i]=now[a[i]];
		now[a[i]]=i;
	}
	f[0]=1;
	for(int i=1;i<=n;i++)
	{
		if(!last[i])f[i]=f[i-1];
		else f[i]=(1ll*f[i-1]+(i-last[i]>1?f[last[i]]:0))%mod;
	}
	cout<<f[n]<<endl;
	return 0;
}

T2.泰拳警告

考场唯一做出来的题,可以直接推柿子,每次枚举平局

\[ans=\sum_{i=0}^{n-1}\dbinom{n}{i}\times \frac{p^i}{(p+2)^n}\times \sum_{j=0}^{\lceil\frac{n-i}{2} \rceil-1}\dbinom{n-i}{j} \]

这样是\(n^2\),瓶颈主要在后面,发现后面就是杨辉三角的半行,直接不太好求,考虑\(O(n)\)递推预处理
下一个\(sum\)就是上一个乘上2再加减上一列的末尾项,可以根据杨辉三角的产生方式得到,所以就\(O(n)\)

#include <bits/stdc++.h>
using namespace std;
const int mod=998244353;
const int N=3000050;
inline int ksm(int x,int y)
{
	int s=1;
	for(;y;y>>=1)
	{
		if(y&1)s=1ll*s*x%mod;
		x=1ll*x*x%mod;
	}
	return s;
}
inline int ny(int x){return ksm(x,mod-2);}
int jc[N],inv[N],jcny[N],p1[N],p2[N],bit[N],sum[N];
inline int C(int x,int y)
{
	if(x<y)return 0;
	if(!y)return 1;
	return 1ll*jc[x]*jcny[y]%mod*jcny[x-y]%mod;
}
signed main()
{
	freopen("fight.in","r",stdin);
	freopen("fight.out","w",stdout);
	int n,p;cin>>n>>p;
	jc[0]=jc[1]=inv[1]=jcny[0]=jcny[1]=1;
	for(int i=2;i<=n+5;i++)
	{	
		jc[i]=1ll*jc[i-1]*i%mod;
		inv[i]=1ll*(mod-mod/i)*inv[mod%i]%mod;
		jcny[i]=1ll*jcny[i-1]*inv[i]%mod;
	}
	int pp1=1ll*p*ny(p+2)%mod,pp2=ny(p+2);
	p1[0]=1;p2[0]=1;
	for(int i=1;i<=n;i++)
	{
		p1[i]=1ll*p1[i-1]*pp1%mod;p2[i]=1ll*p2[i-1]*pp2%mod;
	   sum[i]=1ll*sum[i-1]*2%mod;
		if(i&1)sum[i]=(1ll*sum[i]+C(i-1,i/2))%mod;
		else sum[i]=(1ll*sum[i]-C(i-1,i/2-1)+mod)%mod;
	}
	int ans=0;
	for(int i=0;i<n;i++)ans=(1ll*ans+1ll*(i+1)*C(n,i)%mod*p1[i]%mod*p2[n-i]%mod*sum[n-i]%mod)%mod;
	cout<<ans<<endl;
	return 0;
}

T3.万猪拱塔

毒瘤题,基本属于出题人yy
发现有一些性质,\(w\)互不相同这个比较奇怪,思路也就从这里找
可以转化问题,判定对于一个值域的区间\([l.r]\),它所在的格子是否构成矩形
然后就是鬼能想到的构造方式,将整个网格分成\((n+1)\times (m+1)\)\(2\times 2\)的小正方形,部分出界也算,然后将区间内权值对应的格子染黑,这个区间合法当且仅当仅有4个小正方形内部有1个黑格,并且没有小正方形内部有3个黑格
这种判定方式属实不是给人想的,不过也不难理解
image
这就是实现方法,用线段树,维护最小值每次对最小值维护附加信息
考虑修改是\(r\)新加了一个,会有4个小正方形受影响,根据他们内部的\(w\)与当前\(r\)对应的\(w\)的大小关系,对于每个小正方形可以分成有0个,1个,2个,3个格子的\(w\)比当前的小,然后大力分类讨论,就是区间加减1的操作,弄清楚哪一段加哪一段减
一个技巧是把超出边界的都设成正无穷,就不用特判了
最后区间查询,只有最小值等于4的时候才会累加答案,复杂度\(n\log n\)

#include <bits/stdc++.h>
using namespace std;
//#define int long long
inline int read()
{
	int x=0;char ch=getchar();
	while(ch<'0'||ch>'9')ch=getchar();
	while(ch>='0'&&ch<='9')x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
	return x;
}
const int N=200050;
const int mod=998244353;
inline int addd(int x,int y){return x+y>=mod?x+y-mod:x+y;}
inline int doit(int x){return x>=mod?x-mod:x;}
struct tree{
	int l,r,mi,lazy,s1,s2;
}tr[4*N];
inline void qi(int id)
{	
	tr[id].mi=min(tr[id<<1].mi,tr[id<<1|1].mi);
	if(tr[id<<1].mi<tr[id<<1|1].mi)tr[id].s1=tr[id<<1].s1,tr[id].s2=tr[id<<1].s2;
	else if(tr[id<<1].mi>tr[id<<1|1].mi)tr[id].s1=tr[id<<1|1].s1,tr[id].s2=tr[id<<1|1].s2;
	else tr[id].s1=tr[id<<1].s1+tr[id<<1|1].s1,tr[id].s2=addd(tr[id<<1].s2,tr[id<<1|1].s2);
}
void build(int id,int l,int r)
{
	tr[id].l=l;tr[id].r=r;
	if(l==r)
	{
		tr[id].mi=0;tr[id].s1=1;
		tr[id].s2=l;return;
	}	
	int mid=(l+r)>>1;
	build(id<<1,l,mid);build(id<<1|1,mid+1,r);
	qi(id);
}
inline void luo(int id)
{
	if(tr[id].l!=tr[id].r&&tr[id].lazy)
	{
		tr[id<<1].lazy+=tr[id].lazy;
		tr[id<<1|1].lazy+=tr[id].lazy;
		tr[id<<1].mi+=tr[id].lazy;
		tr[id<<1|1].mi+=tr[id].lazy;
		tr[id].lazy=0;
	}
}
void add(int id,int l,int r,int v)
{
	if(l<=tr[id].l&&r>=tr[id].r)
	{
		tr[id].mi+=v;
		tr[id].lazy+=v;return;
	}
	luo(id);int mid=(tr[id].l+tr[id].r)>>1;
	if(r<=mid)add(id<<1,l,r,v);
	else if(l>mid)add(id<<1|1,l,r,v);
	else add(id<<1,l,mid,v),add(id<<1|1,mid+1,r,v);
	qi(id);
}
struct q{int mi,s1,s2;};
q get(int id,int l,int r)
{	
	if(l<=tr[id].l&&r>=tr[id].r)return (q){tr[id].mi,tr[id].s1,tr[id].s2};
	luo(id);int mid=(tr[id].l+tr[id].r)>>1;
	if(r<=mid)return get(id<<1,l,r);
	if(l>mid)return get(id<<1|1,l,r);
	q an1=get(id<<1,l,mid),an2=get(id<<1|1,mid+1,r),ans;
	ans.mi=min(an1.mi,an2.mi);
	if(an1.mi<an2.mi)ans.s1=an1.s1,ans.s2=an1.s2;
	else if(an1.mi>an2.mi)ans.s1=an2.s1,ans.s2=an2.s2;
	else ans.s1=an1.s1+an2.s1,ans.s2=addd(an1.s2,an2.s2);
	return ans;
}
vector <int> a[N];
int px[N],py[N];
inline void gan(int x,int y,int z,int p)
{
	int s=(x<p)+(y<p)+(z<p);
	if(s==0)add(1,1,p,1);
	else if(s==1)
	{
		int ga=(x<p)?x:(y<p?y:z);
		add(1,1,ga,-1);add(1,ga+1,p,1);
	}
	else if(s==2)
	{
		
		int l=(x<p)?x:y,r=((l==y)?z:min(y,z));if(l>r)swap(l,r);
		add(1,1,l,1);add(1,l+1,r,-1);add(1,r+1,p,1);
	}
	else
	{
		if(x>y)swap(x,y);if(x>z)swap(x,z);if(y>z)swap(y,z);
		add(1,1,x,-1);add(1,x+1,y,1);add(1,y+1,z,-1);add(1,z+1,p,1);
	}
}
signed main()
{
	freopen("pig.in","r",stdin);
	freopen("pig.out","w",stdout);
	int n,m;cin>>n>>m;
	for(int i=0;i<=m+1;i++)a[0].push_back(1e9);
	for(int i=1;i<=n;i++)
	{
		a[i].push_back(1e9);
		for(int j=1;j<=m;j++)
		{
			int x=read();
			a[i].push_back(x);
			px[x]=i;py[x]=j;
		}
		a[i].push_back(1e9);
	}	
	for(int i=0;i<=m+1;i++)a[n+1].push_back(1e9);
	int ans=0;build(1,1,n*m);
	for(int i=1;i<=n*m;i++)
	{
		int x=px[i],y=py[i];
		gan(a[x-1][y-1],a[x][y-1],a[x-1][y],i);
		gan(a[x-1][y+1],a[x][y+1],a[x-1][y],i);
		gan(a[x+1][y-1],a[x][y-1],a[x+1][y],i);
		gan(a[x+1][y+1],a[x][y+1],a[x+1][y],i);
		q an=get(1,1,i);
		if(an.mi==4)ans=addd(doit(ans-an.s2+mod),1ll*an.s1*(i+1)%mod);
	}
	cout<<ans<<endl;
	return 0;
}

长见识了

T4.抑郁刀法

也是一道好题,比较神
首先直接dfs的复杂度是和\(k\)有关的,所以基本没分
发现最多只会染出\(n\)种颜色,所以可以用组合数算出最终答案,这样复杂度就与\(k\)无关了
于是可以状压,设\(f_{s,i}\)表示当前已经染色的点集合是\(s\),用了\(i\)种颜色,转移时枚举\(s\)的补集的子集,将它们染成另外一种颜色,显然当且仅当这个子集中内部没有连边才是合法的,大体的转移方程就是\(dp_{s|k,i+1}+=dp_{s,i}\times [check(k)]\)
现在n急剧上升到\(10^5\),这种思路看来并没有扩展性,然而题目已经告诉你这是npc,似乎找不到多项式复杂度做法
再审一遍题,发现有一个奇怪的条件\(m<=n+5\),这个似乎很离谱,但恰恰是本题的突破口
考虑一个点,如果他度数为1,那么可以直接把它从图上删去,答案会累加\(k-1\)的贡献,正确性显然
知道这个往下想,如果度数为2怎么办?
然后就开始神仙构造了,对每个边赋两个权值\(f,g\),分别表示两端的点同色,异色的时候的方案数,一开始显然有\(f=0,g=1\)
那么上面的转移可以写成

\[dp_{s|k,i+1}+=dp_{s,i}\times \prod_{r\in k}f_r\times \prod_{r\in s\to k}g_r \]

其中\(r\)代表边,先枚举\(k\)集合中的边,钦定他们都相同,然后枚举\(s\)\(k\)的边,钦定他们不同
如果一个点度数为2,那么可以缩点,将这个点删去,然后在他连的两个点之间连一条新边,新的\(f,g\)就是

\[f=f_1\times f_2+(k-1)\times g_1\times g_2 \]

\[g=(k-2)\times f_1\times f_2+f_1\times g_2+f_2\times g_1 \]

画图理解一下,分别考虑删去的这个点颜色与两边的相同还是不同,讨论一下,不难理解
如果我们一直重复这个过程,最后所有点的度数都大于等于3,因为\(m<=n+5\),所以最后剩下的点数不超过10,就可以状压了
因为\(f,g\)的存在,将一个点直接删去时的贡献是\((k-1)\times g+f\),也是分别计算相同和不同
过程中可能会出现重边,可以直接将重边缩到一起,新的\(f,g\)分别是原来的\(f,g\)相乘。对于最后点缩没了(三元环)特判一下
实现方式比较多,但应该都比较复杂,我使用了map+堆实现缩点操作,复杂度\(n\log m\),理论上瓶颈在状压上,实际跑的很快

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int mod=1e9+7;
const int N=100050;
map <int,pair<int,int> > mp[N];
priority_queue <pair<int,int> > q;
struct node{
	int from,to,next,f,g;
}a[40];
int head[40],mm=1;
inline void add(int x,int y,int ff,int gg)
{
	a[mm].from=x;a[mm].to=y;
	a[mm].f=ff,a[mm].g=gg;
	a[mm].next=head[x];head[x]=mm++;
}
int n,m,k,du[N];bool de[N];
vector <int> sta[20];
inline int getsum(int x)
{
	int s=0;
	while(x)
	{	
		if(x&1)s++;
		x>>=1;
	}
	return s;
}
int dp[1<<15][15],mpp[N],tot;
int inv[N],jc[N],jcny[N];
inline int C(int x,int y)
{
	if(x<y)return 0;
	if(!y)return 1;
	return jc[x]*jcny[y]%mod*jcny[x-y]%mod;
}
signed main()
{
	freopen("knife.in","r",stdin);
	freopen("knife.out","w",stdout);
	cin>>n>>m>>k;
	jc[0]=jc[1]=inv[1]=jcny[0]=jcny[1]=1;
	for(int i=2;i<=k;i++)
	{	
		jc[i]=jc[i-1]*i%mod;
		inv[i]=(mod-mod/i)*inv[mod%i]%mod;
		jcny[i]=jcny[i-1]*inv[i]%mod;
	}
	for(int i=1;i<=m;i++)
	{
		int x,y;scanf("%lld%lld",&x,&y);
		mp[x].insert(make_pair(y,make_pair(0,1)));du[x]++;
		mp[y].insert(make_pair(x,make_pair(0,1)));du[y]++;
	}
	int sum=1;
	for(int i=1;i<=n;i++)q.push(make_pair(-du[i],i));
	while(q.size())
	{
		int w=-q.top().first,x=q.top().second;q.pop();
		if(w>=3)break;if(!du[x])continue;
		if(w==1)
		{
			auto p=*mp[x].begin();int y=p.first;
			int f=p.second.first,g=p.second.second;
			mp[y].erase(x);mp[x].erase(y);
			q.push(make_pair(-(--du[y]),y));
			du[x]=0;sum=((k-1)*g%mod+f)%mod*sum%mod;
		}
		if(w==2)
		{
			auto p1=*mp[x].begin(),p2=*mp[x].rbegin();
			int y1=p1.first,f1=p1.second.first,g1=p1.second.second;
			int y2=p2.first,f2=p2.second.first,g2=p2.second.second;
			du[x]=0;mp[x].clear();mp[y1].erase(x);mp[y2].erase(x);
			int f=(f1*f2%mod+(k-1)*g1%mod*g2%mod)%mod;
			int g=((k-2)*g1%mod*g2%mod+f1*g2%mod+f2*g1%mod)%mod;
			auto ga=mp[y1].find(y2);
			if(ga!=mp[y1].end())
			{
				auto p=*ga;int ff=p.second.first,gg=p.second.second;
				int fff=f*ff%mod,ggg=g*gg%mod;
				mp[y1][y2]=make_pair(fff,ggg);
				mp[y2][y1]=make_pair(fff,ggg);
				q.push(make_pair(-(--du[y1]),y1));
				q.push(make_pair(-(--du[y2]),y2));
			}
			else
			{
				mp[y1].insert(make_pair(y2,make_pair(f,g)));
				mp[y2].insert(make_pair(y1,make_pair(f,g)));
			}
		}
	}
	int newsum=0,ans=0;
	for(int i=1;i<=n;i++)
	{
		if(!du[i])continue;
		if(!mpp[i])mpp[i]=++tot;
		for(auto x:mp[i])
		{
			int y=x.first,ff=x.second.first,gg=x.second.second;
			if(!mpp[y])mpp[y]=++tot;
			add(mpp[i],mpp[y],ff,gg);
		}
		newsum++;
	}
	n=newsum;if(!n){cout<<sum*k%mod<<endl;return 0;}
	for(int i=0;i<(1<<n);i++)sta[getsum(i)].push_back(i);
	dp[0][0]=1;
	for(int i=0;i<n;i++)
	{
		for(int j=0;j<sta[i].size();j++)
		{	
			int s=sta[i][j];
			for(int p=0;p<=i;p++)
			{
				if(!dp[s][p])continue;
				int sp=s^((1<<n)-1);
				for(int ss=sp;ss;ss=(ss-1)&sp)
				{
					int f=1,g=1;
					for(int l=1;l<=n;l++)
					{
						if(!((ss>>(l-1))&1))continue;
						for(int ii=head[l];ii;ii=a[ii].next)
						{
							int y=a[ii].to;
							if(!((ss>>(y-1))&1))continue;
							if(y<l)continue;
							f=f*a[ii].f%mod;
						}
					}
					for(int l=1;l<=n;l++)
					{
						if(!((s>>(l-1))&1))continue;
						for(int ii=head[l];ii;ii=a[ii].next)
						{
							int y=a[ii].to;
							if(!((ss>>(y-1))&1))continue;
							g=g*a[ii].g%mod;
						}
					}
					dp[s|ss][p+1]=(dp[s|ss][p+1]+dp[s][p]*f%mod*g%mod)%mod;
				}
			}
		}
	}
	for(int i=1;i<=n;i++)ans=(ans+dp[(1<<n)-1][i]*C(k,i)%mod)%mod;
	cout<<ans*sum%mod<<endl;
	return 0;

遇到npc的图论时想办法把数据范围变小,本质是化归

考试总结

1.不要给自己设限,能想到的都要想到,简单题不要失分
2.对于新奇思路多积累,做一道就有一道,理解要尽量深入
3.留意一些奇怪的数据范围和限制条件,很可能是关键所在

posted @ 2021-09-14 21:46  D'A'T  阅读(59)  评论(0)    收藏  举报