模拟48 考试总结

暑假最后一场

考试经过

开始先想T1,一个多小时想出了正解,后来整了整细节过了三个样例,觉得挺稳就交了,T2不会先开T3,二分之后不会\(check\),想了个不知道多少分的贪心冲上去了,T4最后冲暴力,T2补了一发信仰,然后想着能不能上200
分出来:12+3+0+0=15
输得渣都不剩
T1终究还是挂了,T3真就一分没有,T4又因为返回值RE了
下次再RE我就把电脑屏幕吃掉
心情这事根本说不明白
迷茫 混沌 不知所云.

T1.Lighthouse

考完10分钟A掉了
首先如果没有限制就是\((n-1)!/2\),就是圆排列
看到限制以及20的范围,正难则反直接容斥,枚举子集奇减偶加,枚举选边的情况\(s\),钦定这些边必须选,用并查集维护一下,相当于直接把若干个点强制缩成一个,注意这里由于顺序可以颠倒所以乘2,柿子是

\[ans=(n-|S|-1)!/2 \times2^k \]

这里\(k\)是选择的点中构成的联通块数量
然后就是恶心的特判
首先一个点如果度数大于2说明构不成环不合法
如果选择点中出现了一个点数小于\(n\)的环也不合法
就完了,然而我因为没用并查集判环就死了

#include <bits/stdc++.h>
using namespace std;
#define R register
const int N=10000050;
const int mod=1e9+7;
const int ny2=500000004;
inline int ksm(int x,int y)
{
	int s=1;x%=mod;
	for(;y;y>>=1)
	{
		if(y&1)s=1ll*s*x%mod;	
		x=1ll*x*x%mod;
	}
	return s;
}
int jc[N],a[45],lsh[45],tot;
int fa[42],to[41],size[41],mp[N];
inline int find(int x)
{
	if(fa[x]!=x)fa[x]=find(fa[x]);
	return fa[x];
}
bitset <45> v,vv;int kp[50];
signed main()
{
	int n,m;cin>>n>>m;jc[0]=1;
	for(int i=1;i<=n+10;i++)jc[i]=1ll*jc[i-1]*i%mod;
	for(int i=1;i<=45;i++)kp[i]=ksm(2,i);
	for(int i=1;i<=m;i++)scanf("%d%d",&a[i],&a[i+m]);
 	for(int i=1;i<=2*m;i++)lsh[i]=a[i];
	sort(lsh+1,lsh+2*m+1);
	int cnt=unique(lsh+1,lsh+2*m+1)-lsh-1;
	for(int i=1;i<=2*m;i++)a[i]=lower_bound(lsh+1,lsh+cnt+1,a[i])-lsh;
	int ans=1ll*jc[n-1]*ny2%mod;
	for(R int i=1;i<(1<<m);i++)
	{
		int s=0,ss=0,ga=0,p=0;bool stop=0;
		memset(to,0,sizeof(to));v.reset();vv.reset();
		for(R int j=1;j<=cnt;j++)fa[j]=j,size[j]=1;
		for(R int j=1;j<=m;j++)
		 if((i>>(j-1))&1)
		 {
		 	s++;int x=a[j],y=a[j+m];
		 	to[x]++,to[y]++;
		 	if(to[x]>2||to[y]>2){stop=1;break;}
		 	int fx=find(x),fy=find(y);
		 	if(fx==fy&&size[fx]!=n){stop=1;break;}
		 	fa[fx]=fy;size[fy]+=size[fx],vv[fy]=1;
		 }
		if(stop)continue;
		int mu=0;
		for(R int j=1;j<=cnt;j++)
		{
			int f=find(j);
			if(!v[f]){v[f]=1,ss++;if(vv[f])mu++;}
		}
		ss=cnt-ss;
		int sum=1ll*jc[n-ss-1]*ny2%mod*kp[mu]%mod;
		if(s&1)ans=(1ll*ans-sum+mod)%mod;
		else ans=(1ll*ans+sum)%mod;		
	}
	cout<<ans<<endl;
	return 0;
}

T2.Miner

欧拉路
答案其实不难,是

\[\sum_{i-1}^kmax(1,c_i/2)-1 \]

\(k\)是联通块数量,\(c\)是每个连通块奇点个数
挺好理解的,两个奇点一笔画
构造方案就是在每个连通块中给每一个奇点与另外的一个奇点连边,注意是配对不是乱连,只会出来\(c/2\)条边,然后跑欧拉路就行

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N=100050;
struct node{
	int from,to,next;
}a[4*N];
int head[4*N],mm=2;
inline void add(int x,int y)
{	
	a[mm].from=x;a[mm].to=y;
	a[mm].next=head[x];head[x]=mm++;
}
int du[N],p[N],mp[N];bool v[4*N];
int dfs(int x,int pp)
{
	v[x]=1;p[x]=pp;int ans=(du[x]&1);
	for(int i=head[x];i;i=a[i].next)
	{
		int y=a[i].to;
		if(v[y])continue;
		ans+=dfs(y,pp);
	}
	return ans;
}
vector <int> f[N],an;
stack <int> st;
stack <pair<int,int> >cpu;
void dfss(int r)
{
	cpu.push(make_pair(r,0));
	while(cpu.size())
	{
		int x=cpu.top().first,vv=cpu.top().second,i=head[x];
		while(i&&v[i])i=a[i].next;
		if(i)
		{
			int y=a[i].to;
			cpu.push(make_pair(y,1));
			v[i]=v[i^1]=1;
			head[x]=a[i].next;
		} 
		else 
		{if(vv)st.push(x);cpu.pop();}
	}
}/**/
/*void dfss(int x)
{
	for(int i=head[x];i;head[x]=i=a[i].next)
	{
		int y=a[i].to;
		if(v[i])continue;
		v[i]=v[i^1]=1;
		dfss(y);st.push(y);
	}
}*/
signed main()
{
	int n,m;cin>>n>>m;
	for(int i=1;i<=m;i++)
	{
		int x,y;scanf("%lld%lld",&x,&y);
		add(x,y);add(y,x);
		du[x]++;du[y]++;
	}
	int ans=0,cnt=0;
	for(int i=1;i<=n;i++)
	 if((!v[i])&&du[i])ans+=max((int)1,dfs(i,++cnt)/2),mp[cnt]=i;
	printf("%lld\n",ans-1);
	for(int i=1;i<=n;i++)if(du[i]&1)f[p[i]].push_back(i);
	memset(v,0,sizeof(v));
	int num=n;
  	for(int i=1;i<=cnt;i++)
	{
		num++;
		for(int j=3;j<f[i].size();j+=2)
		{
			int p1=f[i][j-1],p2=f[i][j];
			add(p1,num);add(num,p1);
			add(p2,num);add(num,p2);
		}
		if(f[i].size()&1)
		{
			int ga=f[i][f[i].size()-1];
			add(ga,num);add(num,ga);	
		}
		int ga=f[i].size()?f[i][0]:mp[i];dfss(ga);
		an.clear();
		while(st.size())an.push_back(st.top()),st.pop();
		
		if(i==1)printf("%lld\n",ga);
		else printf("1 %lld\n",ga);
		for(int j=0;j<an.size();j++)
		{
			if(an[j]==num)continue;
			if(j!=0&&an[j-1]==num)printf("1 %lld\n",an[j]);
			else printf("0 %lld\n",an[j]);
		}
	}
	return 0;	 
}

连边我建了虚点,方便记录答案
由于这个算法有很深的递归,所以模拟栈实现
然而一直不对,发现模拟的时候第一个点是不能记录答案的,所以判掉
可以比对一下真正的dfs和模拟的,调了好久

T3.Lyk Love painting

一眼二分,判断用dp搞
首先如果有格子数字大于当前的二分值直接判断不合法
\(f_i\)表示盖满前\(i\)个格子最少用的画数量
转移之前预处理出三个数组\(f_1,f_2,f_3\),具体表示当前的一个格子如果只在第一行放画,第二行放画,一二行放画最远保证合法放的位置,建议看代码
转移首先用\(f_i=f_{f3_i}+1\),然后用两个指针跳一下,同样看代码
复杂度\(nmlogS\)

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N=100050;
int a[3][N],s1[N],s2[N],s3[N];
int f1[N],f2[N],f3[N],f[N];
int n,m;
inline void gan(int x)
{
	f1[0]=f2[0]=f3[0]=0;
	for(int i=1;i<=n;i++)
	{
		int l=0,r=i,ans;
		while(l<=r)
		{	
			int mid=(l+r)>>1;
			if(s1[i]-s1[mid]<=x)ans=mid,r=mid-1;
			else l=mid+1;
		}
		f1[i]=ans;l=0;r=i;
		while(l<=r)
		{
			int mid=(l+r)>>1;
			if(s2[i]-s2[mid]<=x)ans=mid,r=mid-1;
			else l=mid+1;
		}
		f2[i]=ans;l=0;r=i;
		while(l<=r)
		{
			int mid=(l+r)>>1;
			if(s3[i]-s3[mid]<=x)ans=mid,r=mid-1;
			else l=mid+1;
		}		
		f3[i]=ans;
	}
}
inline bool check(int x)
{
	for(int i=1;i<=n;i++)
	 if(a[1][i]>x||a[2][i]>x)return 0;
	gan(x);memset(f,0x3f,sizeof(f));f[0]=0;
	for(int i=1;i<=n;i++)
	{
		f[i]=f[f3[i]]+1;
		int l1=i,l2=i,sum=0;
		while(sum<m&&(l1||l2))
		{
			if(l1>=l2)l1=f1[l1];
			else l2=f2[l2];
		   sum++;
		   f[i]=min(f[i],f[max(l1,l2)]+sum);
		}
	}
	if(f[n]<=m)return 1;
	return 0;
}
inline int er(int l,int r)
{
	if(l>=r)return r;
	int mid=(l+r)>>1;
	if(check(mid))return er(l,mid);
	else return er(mid+1,r);
}
signed main()
{
	cin>>n>>m;
	for(int i=1;i<=2;i++)
	 for(int j=1;j<=n;j++)
	  scanf("%lld",&a[i][j]);
	for(int i=1;i<=n;i++)
	{
		s1[i]=s1[i-1]+a[1][i];
		s2[i]=s2[i-1]+a[2][i];
		s3[i]=s3[i-1]+a[1][i]+a[2][i];
	}	
	cout<<er(1,1e12);
	return 0;
}

T4.Revive

先化减柿子然后搞数据结构,咕了

考试总结

任何时候不要有“我能AC”之类的错觉
事实上是个暴力都比你分高
时间啊 真的不多了

posted @ 2021-08-27 14:00  D'A'T  阅读(84)  评论(0)    收藏  举报