12 月 cls 记录

2021.12.13:

南京大屠杀纪念日,默哀。


HNOI2011 卡农

十年前的题我也是望尘莫及,哈哈!这是一个神奇的 DP 题。

考虑它的条件:在 \(n\) 的所有子集中挑出 \(m\) 个,

  1. 任意集合非空;

  2. 任意集合最多出现一次;

  3. 任意数字出现次数为偶数次。

\(f_i\) 为挑出了 \(i\) 个集合满足条件的方案数。

这样的状态看似不合理,没有办法毫无联系,难以转移,却可以通过容斥转移。

考虑只要确定了前面的,由第三条就可以确定最后一个,故此时方案为 \(P^{i-1}_{2^n-1}\)

考虑第一条,即前面的集合不能恰好满足每一个都是偶数,所以减去 \(f_{i-1}\)

考虑第二条,假设前面有一个 \(j\) 使得集合 \(i,j\) 相等,那么此时剩下的 \(i-2\) 个集合是满足条件的,而 \(j\)\(i-1\) 种选法,集合 \(i,j\)\(2^n-1-(i-2)\) 种选法,故减去 \((i-2)(2^n-i+1)f_{i-2}\)

综上,\(f_i=P^{i-1}_{2^n-1}-f_{i-1}-(i-2)(2^n-i+1)f_{i-2}\)

注意,我们一直是按有序算的,故最后还要除以 \(m!\)

#include<bits/stdc++.h>
using namespace std;
#define inf 1e9
const int maxn=1e6+10;
const int mod=1e8+7;
inline int read(){
	int x=0,f=1;char c=getchar();
	while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
	while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+c-'0';c=getchar();}
	return x*f;
}
inline int ksm(int x,int y){
	int res=1;
	while(y){
		if(y&1)res=1ll*res*x%mod;
		x=1ll*x*x%mod;y>>=1;
	}return res;
}
int n,m,f[maxn],fac=1;
int main(){
	n=read(),m=read();
	int pw=ksm(2,n),cur=pw-1;
	f[0]=1,f[1]=0;
	for(int i=2;i<=m;i++){
		f[i]=((cur-f[i-1]-1ll*(i-1)*(pw-i+1)%mod*f[i-2]%mod)%mod+mod)%mod;
		cur=1ll*cur*(pw-i)%mod;fac=1ll*fac*i%mod;
	}printf("%d\n",1ll*f[m]*ksm(fac,mod-2)%mod);
    return 0;
}

深深地感到自己的弱小。


2021.12.21:

考的 \(pnershy\) 的题,不想改。第一题是三维偏序,别人都用的二维树状数组,只有我傻乎乎地用 \(cdq\),第二题是 \(bitset\) 优化二进制分组背包,而我只会傻乎乎地分治 \(ntt\)。学到了,判断和为 \(n\) 的一堆数能否组成 \(k\) 的最优复杂度不是 \(n\log^2 n\) 而是 \(n\sqrt n\log n/w\)

补了昨天 \(demonlover\) 的题。其中的 \(cf1120d\) 比较有意思。每个节点相当于操作一个区间,也相当于操作差分数组的两个点,所以想到最小生成树。判断每条边有无可能在最小生成树上,就是对于每一个权值,之前的随意取一棵最小的,然后判断当前可不可以加入。是个有意思的小 \(trick\)

#include<bits/stdc++.h>
using namespace std;
#define inf 1e9
const int maxn=2e5+10;
const int mod=1e9+7;
inline int read(){
	int x=0,f=1;char c=getchar();
	while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
	while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+c-'0';c=getchar();}
	return x*f;
}
#define ll long long
int n,m,w[maxn],tot,ok[maxn];ll ans;
int beg[maxn],nex[maxn<<1],to[maxn<<1],e;
inline void add(int x,int y){
	e++;nex[e]=beg[x];beg[x]=e;to[e]=y;
	e++;nex[e]=beg[y];beg[y]=e;to[e]=x;
}
int l[maxn],r[maxn],ti,f[maxn];
struct node{int x,y,z,id;}E[maxn];
inline void dfs(int x,int fa){
	int flag=0;l[x]=ti+1;
	for(int i=beg[x];i;i=nex[i]){
		int t=to[i];
		if(t==fa)continue;
		dfs(t,x);flag=1;
	}if(!flag)++ti;r[x]=ti;
	E[x]=(node){l[x],r[x]+1,w[x],x};
	//printf("%d [%d,%d]\n",x,l[x],r[x]);
}
inline int cmp(node x,node y){return x.z<y.z;}
inline int find(int x){return x==f[x]?x:f[x]=find(f[x]);}
int main(){
	freopen("crops.in","r",stdin);
	freopen("crops.out","w",stdout);
	n=read();
	for(int i=1;i<=n;i++)
		w[i]=read();
	for(int i=1,x,y;i<n;i++)
		x=read(),y=read(),add(x,y);
	dfs(1,0);sort(E+1,E+1+n,cmp);
	for(int i=1;i<=n;i++)f[i]=i;
	for(int i=1,j=1;i<=n;i=j){
		for(j=i+1;j<=n&&E[i].z==E[j].z;j++);
		for(int k=i;k<j;k++)
			if(find(E[k].x)!=find(E[k].y))
				tot++,ok[E[k].id]=1;
		for(int k=i;k<j;k++)
			if(find(E[k].x)!=find(E[k].y))
				ans+=E[k].z,f[find(E[k].x)]=find(E[k].y);
	}printf("%lld %d\n",ans,tot);
	for(int i=1;i<=n;i++)
		if(ok[i])printf("%d ",i);puts("");
    return 0;
}

补了一下之前 vp 的 Educational Round 94,里面的 F 和 G 比较有意思。

CF1400G Mercenaries

有时候真的是奇奇怪怪的题目做多了,反而想不到最简单、最本真、最自然的想法。

看到 \(m\le 20\),我还在想什么呢,容斥啊!\(Ans=\sum\limits_{T\subseteq S} (-1)^{|T|+1} f(T)\),其中 \(f(T)\) 表示硬选这些边的方案数。

剩下的部分就是容易的了,还有就是要预处理一个前缀和。

补了之前考时没做出来的题,喜提一场绿。CF1617E Christmas Chocolates

抽象成一个图论问题。无向图,无穷个点,若 \(x+y=2^k\) 则有边,求 \(n\) 个特殊点中两两最短路的最大值。

因为是无向图,我们考虑大的往小的连边。我们惊喜地发现这样的边只有一条,即 \(2^k\) 恰好大于 \(x\) 时有解。易证。

于是原图退化成了一棵 \(n\log n\) 级别个点的树。求两叶子节点间的最大距离即可。随意实现。我的 \(bfs\) 实现比较诡异。

#include<bits/stdc++.h>
using namespace std;
#define inf 1e9
const int maxn=2e5+10;
const int mod=1e9+7;
inline int read(){
	int x=0,f=1;char c=getchar();
	while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
	while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+c-'0';c=getchar();}
	return x*f;
}
#define fi first
#define se second
#define pii pair<int,int>
#define mkp make_pair
map<int,pii>mp;
inline int f(int x){for(int i=0;;i++)if((1<<i)>=x)return (1<<i);}
int n,m,k,s,u,v,ans;
int main(){
	n=read();
	for(int i=1,x;i<=n;i++)
		x=read(),mp[x]=mkp(0,i);
	while(k=(--mp.end())->fi){
		s=f(k)-k;
		if(mp.find(s)!=mp.end()&&ans<mp[s].fi+mp[k].fi+1){
			ans=mp[s].fi+mp[k].fi+1;u=mp[s].se,v=mp[k].se;
		}if(mp[s].fi<mp[k].fi+1)mp[s]=mkp(mp[k].fi+1,mp[k].se);
		mp.erase(k);
	}printf("%d %d %d\n",u,v,ans);
    return 0;
}

复习了 \(\text{AC}\) 自动机。做了 P3121 [USACO15FEB]Censoring G


2021.12.22:

上午考 \(codycode\) 的比赛,\(\text{T3}\) 简单题,注意 \(dp\) 函数的单调性即可。

\(AC\) 机题意外收获一个树上 \(trick\),给一堆节点到跟的路径加 \(1\):按 \(dfs\) 序排序,\(p_i+1\)\(lca(p_i,p_{i+1})-1\)

\(verdandi\) 联手 \(vp\)The 2021 China Collegiate Programming Contest (Harbin)题解

补了一下昨天的鸽掉的 \(cf1400f\),感觉最近碰到的 \(\text{AC}\) 自动机题好多,晕。

大概就是字符串和数论的结合是不好做的,考虑到 \(X\) 极小,不妨生成所有的 \(X-prime\) 串。这时题意变为最少删除几个使不包含任一生成串。对生成串建 \(AC\) 自动机,设 \(dp_{i,j}\) 表示考虑到第 \(i\) 位,此时在自动机上走到节点 \(j\) 所需的最小删除个数。两种情况,若下一个删除则有 \(dp_{i,j}+1\rightarrow dp_{i+1,j}\),否则若下一个节点不是 \(endpos\) 则有 \(dp_{i,j}\rightarrow dp_{i+1,ch[j][s[i]-'0']}\) ,数组滚一下。

#include<bits/stdc++.h>
using namespace std;
const int inf=1e9;
const int maxn=1e6+10;
const int mod=1e9+7;
inline int read(){
	int x=0,f=1;char c=getchar();
	while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
	while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+c-'0';c=getchar();}
	return x*f;
}
int n,m,k,dp[2][maxn];
int ch[maxn][10],cnt,fa[maxn],edp[maxn];
inline void insert(string ss){
	int u=0;
	for(int i=0;i<ss.size();i++){
		int t=ss[i]-'0';
		if(!ch[u][t])ch[u][t]=++cnt;
		u=ch[u][t];
	}edp[u]=1;
}
inline void chk(string ss){
	for(int i=0;i<ss.size();i++){
		int S=0;
		for(int j=i;j<ss.size();j++){
			S+=ss[j]-'0';
			if(k>S&&k%S==0)return;
		}
	}insert(ss);
}
inline void gen(string ss,int S){
	if(S>k)return;
	if(S==k)return void(chk(ss));
	for(int i=1;i<=9;i++)
		gen(ss+(char)(i+'0'),S+i);
}
queue<int>q;
inline void build(){
	for(int i=0;i<=9;i++)
		if(ch[0][i])q.push(ch[0][i]);
	while(!q.empty()){
		int x=q.front();q.pop();
		for(int i=0;i<=9;i++){
			int &v=ch[x][i];
			if(v)fa[v]=ch[fa[x]][i],q.push(v);
			else v=ch[fa[x]][i];
		}
	}
}
string s;
inline void chkmin(int &x,int y){x=(x<y?x:y);}
int main(){
	cin>>s;n=s.size();
	k=read();gen("",0);//puts("over");
	build();
	memset(dp,0x3f,sizeof(dp));
	dp[0][0]=0;
	for(int i=1;i<=n;i++){
		for(int j=0;j<=cnt;j++)dp[i&1][j]=inf;
		for(int j=0;j<=cnt;j++){
			chkmin(dp[i&1][j],dp[~i&1][j]+1);
			if(!edp[ch[j][s[i-1]-'0']])
				chkmin(dp[i&1][ch[j][s[i-1]-'0']],dp[~i&1][j]);
		}
	}int ans=inf;
	for(int i=0;i<=cnt;i++)
		chkmin(ans,dp[n&1][i]);
	printf("%d\n",ans);
    return 0;
}

2021.12.23:

做了一道 \(usaco\) 的题 [USACO21FEB] No Time to Dry P

尽量多涂,对于相邻的两个相同的,若其中间没有比它们小的就可以减少一次,预处理出这样的区间,容斥一手即可。

原文地址:https://www.cnblogs.com/syzf2222/p/15681683.html