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\)
循环卷积
由于今晚要补一下循环卷积(代码咕咕咕~)