3.27省选补题

\(3.27\)题目选做

\(P3349\)小星星

\(O(n^33^n)DP\)显然过不了,考虑优化

我们考虑在不限制选择数字(可以重复)的情况下进行\(DP\),然后将不合法的容斥出去

大概可以理解,就是我们在全部选择数字的情况下,我们肯定有选不到的数字,然后减去集合小的方案数就好了,然后加上下一个集合(类似于二项式,其实是子集容斥)

\(20pts\)暴力\(dp\)

#include<bits/stdc++.h>
#define MAXN 40
using namespace std;
int dp[18][18][1<<18],mid[18][1<<18],n,m,u,v;
int head[MAXN],nxt[MAXN],to[MAXN],tot;
bool dis[20][20];
void add(int u,int v)
{
	 tot++;
	 to[tot]=v;
	 nxt[tot]=head[u];
	 head[u]=tot;
}
void dfs(int now,int fa)
{
	 for(int i=1;i<=n;i++)
	 {
	 	 dp[now][i][1<<(i-1)]=1;
	 }
	 for(int i=head[now];i;i=nxt[i])
	 {
	 	 int y=to[i];
	 	 if(y==fa) continue;
	 	 dfs(y,now);
	 	 memcpy(mid,dp[now],sizeof(dp[now]));
	 	 for(int j=1;j<=n;j++)
	 	 {
	 	 	 for(int k=1;k<(1<<n);k++)
	 	 	 {
                 if(!((k>>(j-1))&1)) continue;
//                 cout<<"now: "<<now<<" "<<j<<" "<<k<<endl;
	 	 	 	 for(int z=(k-1)&k;z;z=(z-1)&k)
	 	 	 	 {
	 	 	 	 	 for(int x=1;x<=n;x++)
	 	 	 	 	 {
	 	 	 	 	 	 if(x==j) continue;
	 	 	 	 	 	 if(!dis[x][j]||!((z>>(x-1))&1)) continue;
	 	 	 	 	 	 dp[now][j][k]+=mid[j][k^z]*dp[y][x][z];
//	 	 	 	 	 	 cout<<"zy: "<<y<<" "<<x<<" "<<z<<" "<<dp[y][x][z]<<endl;
	 	 	 	 	 }
	 	 	 	 }
//					system("pause");
//	 	 	 	 cout<<"now: "<<now<<" "<<j<<" "<<k<<" "<<dp[now][j][k]<<endl;
	 	 	 }
	 	 }
	 }
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++)
    {
    	scanf("%d%d",&u,&v);
    	dis[u][v]=dis[v][u]=1;
    }
    for(int i=1;i<n;i++)
    {
    	scanf("%d%d",&u,&v);
    	add(u,v);add(v,u);
    }
    dfs(1,1);
    int res=0;
	for(int i=1;i<=n;i++)
	{
		res+=dp[1][i][(1<<n)-1];
	}
    cout<<res<<endl;
}

\(100pts\)容斥

#include<bits/stdc++.h>
#define int long long
#define MAXN 50
using namespace std;
int Ans,cnt,dp[MAXN][MAXN],Mid[MAXN][MAXN],Xuan[MAXN];
int head[MAXN],nxt[MAXN],to[MAXN],tot,n,m;
bool dis[MAXN][MAXN],vis[MAXN];
void add(int u,int v)
{
	 tot++;
	 to[tot]=v;
	 nxt[tot]=head[u];
	 head[u]=tot;
}
void dfs(int now,int fa)
{
	 for(int i=1;i<=cnt;i++)
	 {
	 	 dp[now][Xuan[i]]=1;
	 }
	 for(int i=head[now];i;i=nxt[i])
	 {
	 	 int y=to[i];
	 	 if(y==fa) continue;
	 	 dfs(y,now);
	 	 for(int z=1,res=0;z<=cnt;z++)
	 	 {
	 	 	 res=0;
	 	 	 for(int j=1;j<=cnt;j++)
			 {
	 	 	     if(!dis[Xuan[z]][Xuan[j]])	continue;
	 	 	     res+=dp[y][Xuan[j]];
	 	 	 }
	 	 	 dp[now][Xuan[z]]*=res;
	 	 }
	 }
//	 for(int i=1;i<=cnt;i++)
//	 {
//         cout<<dp[now][Xuan[i]]<<" ";
//	 }
//	 cout<<endl;
}
void solve()
{
     cnt=0;
     memset(dp,0,sizeof(dp));
     for(int i=1;i<=n;i++) if(vis[i]) Xuan[++cnt]=i;
     dfs(1,1);
     int flag=(n-cnt)%2==1?-1:1;
     for(int i=1;i<=cnt;i++)
     {
     	 Ans+=flag*dp[1][Xuan[i]];
     }
}
void dfs_num(int now)
{
	 if(now==n+1) return solve();
	 vis[now]=false;  dfs_num(now+1);
	 vis[now]=true; dfs_num(now+1);
}
signed main()
{
	scanf("%lld%lld",&n,&m);
	for(int i=1,u,v;i<=m;i++)
	{
		scanf("%lld%lld",&u,&v);
		dis[u][v]=dis[v][u]=1;
	}
    for(int i=1,u,v;i<n;i++)
    {
    	scanf("%lld%lld",&u,&v);
    	add(u,v);add(v,u);
    }
    dfs_num(1);
    cout<<Ans<<endl;
}

省选测试

\(T1\)魔法球

二分答案显然,还是考虑贪心,赛时想了一个\(n^2log\)的贪心策略,找到优化之后想的很麻烦就没写

考后看到一个仙术(尽管是错的但是数据太水没有卡...)

其实正解和想到的优化一样,我的实现却很复杂,就是每次找最小的数进行权值加\(1\),其实每次都是桶移动,然后用一个链表维护一下就好了

以下是\(std,\)我的神奇的不对做法就不放了...

#include<bits/stdc++.h>
int n,a[1<<20];
struct linker
{
	int pre,nxt,cnt;
}L[1<<20];
int cPointer,cSum;
void incP()
{
	cPointer=L[cPointer].nxt;
	cSum+=L[cPointer].cnt;
}
void decP()
{
	cSum-=L[cPointer].cnt;
	cPointer=L[cPointer].pre;
}
void eraseNode(int p)
{
	if(cPointer==p)incP();
	int f=L[p].pre,b=L[p].nxt;
	L[f].nxt=b,L[b].pre=f;
}
void addFirstK(int d)
{
	while(cSum<d)incP();
	//puts("----");
	while(cSum-L[cPointer].cnt>=d)
		decP();
	int Lc=cSum-d,Rc=L[cPointer].cnt-Lc;
	//printf("*l %d->%d *r %d->%d\n",
	//L[cPointer].pre,Lc,L[cPointer].nxt,Rc);
	if(L[cPointer].pre)
	{
		L[L[cPointer].pre].cnt+=Lc,L[L[cPointer].nxt].cnt+=Rc;
		cSum-=Rc;
		eraseNode(cPointer);
	}
	else
	{
		L[L[cPointer].nxt].cnt+=Rc;
		L[cPointer].cnt=Lc;
		cSum-=Rc;
	}
}
bool valid(int M)
{
	//fprintf(stderr,">%d\n",M);
	for(int i=0;i<=n+1;i++)L[i].pre=i-1,L[i].nxt=i+1,L[i].cnt=0;
	for(int i=1;i<=n-M;i++)L[a[i]].cnt++;
	cPointer=1,cSum=L[1].cnt;
	int cur=n;
	for(int i=1;i<=n-M;i++)
	{
		int Pos=L[n+1].pre;
		while(!L[Pos].cnt)
		{
			eraseNode(Pos);
			Pos=L[Pos].pre;
			cur--;
		}
		if(cur>n-i)return 0;
		if(cur<=M)return 1;
		L[Pos].cnt--;
		if(cPointer>=Pos)cSum--;
		//printf("! op(%d) %d %d\n",cur-M,cPointer,cSum);
		addFirstK(cur-M);
		if(L[n+1].cnt)return 0;
	}
	return 1;
}
void exec()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",a+i);
		if(a[i]>n)a[i]=n;
	}
	std::sort(a+1,a+n+1);
	int L=0,R=n;
	while(L+1<R)
	{
		int M=(L+R)>>1;
		if(valid(M))R=M;
		else L=M;
	}
	printf("%d\n",R);
}
int main()
{
	int T;
	scanf("%d",&T);
	while(T--)exec();
}

\(T2\)

我们可以暴力\(KMP\)拿到\(30pts\)

然后我们发现这道题貌似没有正规解法,我们考虑进行根号分治(简称暴力的结合),然后就过了

考虑两种暴力

\(Sit_1:\) 每次暴力\(KMP\)进行删除操作

\(Sit_2:\)维护一段区间的\(hash\)值,然后合并\(hash\)进行计算,记录一下临界点,把能删的都删了

然后拼起来就好了

\(T3\)

循环卷积

由于今晚要补一下循环卷积(代码咕咕咕~)

posted @ 2022-03-27 18:27  Authentic_k  阅读(24)  评论(0编辑  收藏  举报