[绍兴集训2019]test2-27

文章目录

  • 吐槽
  • 题解
    • T1 肯德基
        • 连通块们选点
        • 联通块的内部构造
        • 质因子分进联通块
        • 最后
    • T2 麦当劳
    • T3 必胜客

吐槽

如果说前三场都是因为各种智障错误爆炸,这场就终于是凭实力爆炸,打了三个暴力,美滋滋。
rank1 3hAK。我还有什么话可说呢。只有ORZ%%%。

其实T3我知道这种最大流求法因为之前做过谁谁养兔子那题,然后我用这玩意打了第二档暴力。。思考了1s是继续想T3还是去做还没有动的T1我非常不明智地选择了后者于是最后1.5h我成功地得到了10pt并且再也没有任何动静。如果继续延续那1s的T3可能用这个东西dp就可以搞搞的思路感觉出正解并不是很困难,,,都知道结论了。。。我大概是睿智本智吧。。。

题解

T1 肯德基

原题在这里,被出题人搬来了
被rank1,2,3切了,其余没有超过10分的。
因为菜宸完全不懂容斥只会枚举有几个数相等然后用组合数做容斥,然后只能过k<=3的点。。显然他们之间的相等关系没有这么简单,某一坨相等为一个数另一坨相等为另个数之类的。

那如何容斥呢。把相等看成边,k个数看成点,某两个数相等就在两个点之间连边,这样枚举完全图的所有子图就相当于完美地枚举所有相等关系了。
于是开始枚举子图了。根据乘法原理,下面三部分的答案是相乘的。

连通块们选点

枚举子图当然是首先枚举每个连通块大小,设分成i个连通块,第x个连通块大小为sz[x],大小为x的连通块有cnt[x]个,把k个点分到这些连通块中的方案就是
! k ∏ ! s z x ∏ ! c n t x \frac{!k}{\prod !sz_x\prod !cnt_x} !szx!cntx!k
宸式感性理解:联通块们排队依次选自己需要的所有点( ! k !k !k),每个连通块在选自己的sz个点时顺序是无所谓的( ∏ ! s z x \prod !sz_x !szx)。一些大小相同的小联通块,他们排队选点的先后顺序也是无所谓的( ∏ ! c n t x \prod !cnt_x !cntx)。

联通块的内部构造

确定了大小后联通块的内部究竟长相如何?我们都知道如何算大小为n的连通块数目,设g[n]为大小为n的联通块数目,f[n]为大小为n的图的数目.枚举1所在联通块
f n = 2 n ∗ ( n − 1 ) 2 g n = f n − ∑ i = 1 n − 1 ( n − 1 i − 1 ) g i ∗ f n − i f_n=2^{\frac{n*(n-1)}{2}} \\g_n=f_n-\sum_{i=1}^{n-1} \binom{n-1} {i-1}g_i*f_{n-i} fn=22n(n1)gn=fni=1n1(i1n1)gifni
现在我们构造若干连通块作为一个图,一个图的容斥系数是 ( − 1 ) e c n t (-1)^{ecnt} (1)ecnt e c n t ecnt ecnt为边数。这玩意显然是可以乘法原理计算每个连通块的容斥系数再相乘的,我们就可以直接算每个连通块的所有连通方式的容斥系数和,然后再相乘。
然后,我们发现把f和g定义为容斥系数和,依然满足 g n = f n − ∑ i = 1 n − 1 ( n − 1 i − 1 ) g i ∗ f n − i g_n=f_n-\sum_{i=1}^{n-1} \binom{n-1} {i-1}g_i*f_{n-i} gn=fni=1n1(i1n1)gifni
算f枚举完全图的所有子图。枚举子图边数
f n = ∑ i = 0 n ∗ ( n − 1 ) / 2 ( − 1 ) i ( n ∗ ( n − 1 ) / 2 i ) = [ n = 1 ] f_n=\sum_{i=0}^{n*(n-1)/2}(-1)^i\binom{n*(n-1)/2}{i}=[n=1] fn=i=0n(n1)/2(1)i(in(n1)/2)=[n=1]
带入得到
g n = − ( n − 1 ) ∗ g n − 1 g_n=-(n-1)*g_{n-1} gn=(n1)gn1

质因子分进联通块

哎呀我的妈呀终于把图构造好了万事俱备只欠数进去了。质因子显然也是互不相关可以方案相乘的,且放质因子进图的方案和它本人是啥子没有关而只和它的数目有关。
第一部分的答案我们对每种连通块大小进行搜索,第二部分在搜索的过程中乘上每一块的g,现在这部分就在搜索的过程中搞一个dp, d p [ i ] [ j ] dp[i][j] dp[i][j]表示把j个质因子放进目前搜到的i个块中的方案数,转移会发现可以做成一个以每个块的大小为物品体积的完全背包(意会吧不扯了)。

最后

一些优化效率的小trick。
搜索的时候肯定是按块的大小从小往大搜,剩下的数的个数是last,上一个块大小是psz时,下一个块的大小只需要搜psz~last/2和last。
完全背包是一维来着,开两维是为了回溯上一层的背包状态,一边搜一边背,搜完再背可能会TLE。

//Achen
#include
#define For(i,a,b) for(int i=(a);i<=(b);i++)
#define Rep(i,a,b) for(int i=(a);i>=(b);i--)
#define Formylove return 0
const int N=55555,mod=1e9+7;
typedef long long LL;
typedef double db;
using namespace std;
int n,k;
int p[N],c[N],mx;
LL ans,fac[N],inv[N],g[N];

template<typename T>void read(T &x) {
	char ch=getchar() ; T f=1; x=0;
	while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
	if(ch=='-') f=-1,ch=getchar();
	for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0'; x*=f;
}

LL mo(LL x) { return x<0?x+mod:(x>=mod?x-mod:x); }

LL C(int n,int m) { return fac[n]*inv[m]%mod*inv[n-m]%mod; }
LL fen(int n,int m) { return C(n+m-1,m-1); }

void pre() {
	fac[0]=fac[1]=1;
	inv[0]=inv[1]=1;
	For(i,2,k) fac[i]=fac[i-1]*i%mod;
	For(i,2,k) inv[i]=mo(mod-(LL)mod/i*inv[mod%i]%mod);
	For(i,2,k) inv[i]=inv[i-1]*inv[i]%mod;
	For(i,2,n) {
		int fl=1,up=sqrt(i);
		For(j,2,up) if(i%j==0) {
			fl=0; break;
		}
		if(fl) p[++p[0]]=i;
	}
	For(i,2,n) {
		int x=i;
		For(j,1,p[0]) {
			while(x%p[j]==0) {
				c[j]++;
				x/=p[j];
			}
		}
	}
	mx=c[1];
	g[1]=1;
	For(i,2,k) g[i]=mo(mod-g[i-1]*(i-1)%mod);
}

LL f[32][N];
void dfs(int pos,int psz,int pcnt,int last,LL now) {
	if(!last) {
		For(i,1,p[0]) now=now*f[pos-1][c[i]]%mod;
		ans=mo(ans+now);
		return ; 
	}
	if(last<psz) return ;
	if(psz) {
		For(s,0,mx) f[pos][s]=mo(f[pos-1][s]+(s>=psz?f[pos][s-psz]:0));
		dfs(pos+1,psz,pcnt+1,last-psz,now*g[psz]%mod*inv[psz]%mod*fac[pcnt]%mod*inv[pcnt+1]%mod);
	}
	For(x,psz+1,last/2) {
		For(s,0,mx) f[pos][s]=mo(f[pos-1][s]+(s>=x?f[pos][s-x]:0));
		dfs(pos+1,x,1,last-x,now*g[x]%mod*inv[x]%mod); 
	}
	if(psz!=last) { int x=last;
		For(s,0,mx) f[pos][s]=mo(f[pos-1][s]+(s>=x?f[pos][s-x]:0));
		dfs(pos+1,x,1,last-x,now*g[x]%mod*inv[x]%mod); 
	}
}

int main() {
	freopen("noip.in","r",stdin);
	freopen("noip.out","w",stdout);
	read(n); read(k);
	pre(); f[0][0]=1;
	dfs(1,0,0,k,1);
	printf("%lld\n",ans);
	Formylove;
}

T2 麦当劳

出题人:这是一道noip简单题。

对于 c b a ∣ a b c d e f ∣ f e cba|abcdef|fe cbaabcdeffe这样没有出现过完整ab的串一遍马拉车扫过去就可以得到答案。
考虑出现过完整循环的形如 b a ∣ a b c d d c b a ∣ a b c d d c b a ∣ a b c d d c b a ∣ a b c d d ba|abcddcba|abcddcba|abcddcba|abcdd baabcddcbaabcddcbaabcddcbaabcdd的串

我们知道循环部分是可以kmp找循环节的,但是前后有一部分不完整的部分,这非常不好考虑,于是智障宸就只会枚举左边多出的部分剩下的部分跑kmp,这样怎么复杂度都炸了,不如直接打暴力。
然鹅,显然这样的串都可以通过前移这个|改变划分变成只有后面有多出部分的形式,我们先只考虑他这样的形态就好了。
b a ∣ a b c d d c b a ∣ a b c d d c b a ∣ a b c d d c b a ∣ a b c d d ba|abcddcba|abcddcba|abcddcba|abcdd baabcddcbaabcddcbaabcddcbaabcdd

b a a b c d d c ∣ b a a b c d d c ∣ b a a b c d d c ∣ b a a b c d d baabcddc|baabcddc|baabcddc|baabcdd baabcddcbaabcddcbaabcddcbaabcdd
然后就可以跑一遍kmp判断这个串是否有一个长度为l的循环节了。且如果有一个长度为l的循环节,任意一个长度为l的子串都是这个串的某种循环划分的一个循环节。

我们需要找循环节是回文串的那些划分,只要找到向左右扩展大于等于l/2的回文中心,就能找到所有划分点和两个划分点中间的回文中心点了(因为上一段那句话)。
会找到形如这样的(每种合法的情况的点都找到了,下面是某种合法的情况)
b a a b c d ∣ d c b a ∣ a b c d ∣ d c b a ∣ a b c d ∣ d c b a ∣ a b c d d baabcd|dcba|abcd|dcba|abcd|dcba|abcdd baabcddcbaabcddcbaabcddcbaabcdd
每个完整的块就是题目中要求的一个合法答案,然鹅这样的话找到的划分点是比完整块数少1的(最前和最后的完整块的左右没有被包起来呢)!也就是每次答案应该再加上划分的方式的种数。
那很好办啊,找到 [ l , 2 ∗ l ) [l,2*l) [l,2l)内的划分点的数目就好了,这些划分点一定是一种划分的开始点啊。

现在只需要支持找大于等于l的回文中心数目,和他们中在某个区间内的数目,按l排序后树状数组搞一搞即可。

//Achen
#include
#define For(i,a,b) for(int i=(a);i<=(b);i++)
#define Rep(i,a,b) for(int i=(a);i>=(b);i--)
#define Formylove return 0
const int N=2000007;
typedef long long LL;
typedef double db;
using namespace std;
int n;
char s[N],ss[N];
LL ans;

template<typename T> void read(T &x) {
	char ch=getchar(); T f=1; x=0;
	while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
	if(ch=='-') f=-1,ch=getchar();
	for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0'; x*=f;
}

int rad[N],r[N];
void manacher() {
	int tot=0;
	ss[++tot]='&';
	ss[++tot]='#';
	For(i,0,n-1) {
		ss[++tot]=s[i];
		ss[++tot]='#';
	}
	ss[++tot]='*';
	for(int i=1,k=0,j;i<=tot;) {
		while(ss[i-k-1]==ss[i+k+1]) k++;
		rad[i]=k;
		for(j=1;j<=k&&rad[i-j]!=rad[i]-j;j++)
			rad[i+j]=min(rad[i-j],rad[i]-j);
		i+=j;
		k=max(k-j,0);
	}
	int cc=1,nl=0;
	For(i,1,n) {
		r[i]=rad[(i+1)*2];
		while(nl+1<i&&(i-nl-1)>n-i&&(i-nl-1)>nl+1) {
			nl++;
			if(r[nl]/2>=nl) cc++;
		}
		if(r[i]/2>=n-i&&n-i<i) ans+=cc;
	} ans--;
}

int sum[N];
int add(int x,int v) {
	for(int i=x;i<=n;i+=(i&(-i)))
		sum[i]+=v;
}

int qry(int x) {
	int rs=0;
	for(int i=x;i;i-=((i&(-i)))) 
		rs+=sum[i];
	return rs;
}

#define MP make_pair
#define pr pair
#define fi first
#define se second
set<pr>S;

int nxt[N],sl[N];
void kmp() {
	for(int i=1,k=0;i<n;i++) {
		while(k&&s[i]!=s[k]) k=nxt[k-1];
		if(s[i]==s[k]) k++;
		nxt[i]=k;
	}
	int k=nxt[n-1],fl=0;
	for(;;) {
		int l=n-k;
		if(!(l&1)) sl[++sl[0]]=l;
		if(!k) break;
		k=nxt[k-1];
	}
	For(cs,1,sl[0]) {
		int l=sl[cs];
		if(!fl) {
			For(i,1,n-1) if(r[i]>=l) {
				add(i,1);
				S.insert(MP(r[i],i));
			}
			fl=1;
		}
		else {
			while(!S.empty()) {
				pr t=*S.begin();
				if(t.fi<l) {
					add(t.se,-1);
					S.erase(S.begin());
				}
				else break;
			}
		}
		ans+=qry(n)+qry(l-1)-qry(l/2-1);
	}
}

int main() {
    freopen("oip.in","r",stdin);
    freopen("oip.out","w",stdout);
    scanf("%s",s);
	n=strlen(s); 
	manacher();
	kmp();
	printf("%lld\n",ans);
	Formylove;
}

T3 必胜客

原题在这里,被出题人搬来了
给出的列的数组叫b,行的数组就叫a吧。
第二个部分分显然在暗示你,容易想到枚举a的每一位然后网络流判断是否合法。判最大流是否等于b的和即可。然后最大流等于最小割,把a,b排序,最小割就是
c u t = M i n A , B ( ∑ i = 1 A a [ i ] + ∑ i = 1 B b [ i ] + ( n − A ) ∗ ( m − B ) ) cut=Min_{A,B}(\sum_{i=1}^Aa[i]+\sum_{i=1}^Bb[i]+(n-A)*(m-B)) cut=MinA,B(i=1Aa[i]+i=1Bb[i]+(nA)(mB))
满足条件的a就是 c u t ≥ s u m b [ m ] cut \geq sumb[m] cutsumb[m],即任意 A , B A,B A,B,满足
∑ i = 1 A a [ i ] + ∑ i = 1 B b [ i ] + ( n − A ) ∗ ( m − B ) ≥ s u m b [ m ] \sum_{i=1}^Aa[i]+\sum_{i=1}^Bb[i]+(n-A)*(m-B)\geq sumb[m] i=1Aa[i]+i=1Bb[i]+(nA)(mB)sumb[m]

枚举A,B,对于排序后a的每个前缀和,可以得到一个限制, s u m a [ i ] ≥ x [ i ] suma[i] \geq x[i] suma[i]x[i];
既然如此只要dp求出满足这个条件的所有a数组就好啦。
f [ i ] [ j ] [ k ] f[i][j][k] f[i][j][k]表示a中前i小的数已经放进a数组的某些位置了,其中最大的数为j,前i小的数的和为k的方案数,转移需要枚举新加的数放多少个,从f[i-k]用组合数转移到f[i]。j这一维用前缀和优化就好。 o ( n 5 ) o(n^5) o(n5)

//Achen
#include
#define For(i,a,b) for(int i=(a);i<=(b);i++)
#define Rep(i,a,b) for(int i=(a);i>=(b);i--)
#define Formylove return 0
const int N=55,p=1000000007;
typedef long long LL;
typedef double db;
using namespace std;
int n,m,b[N],a[N];
LL ans,C[N][N],f[N][N][N*N];

template<typename T>void read(T &x) {
	char ch=getchar() ; T f=1; x=0;
	while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
	if(ch=='-') f=-1,ch=getchar();
	for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0'; x*=f;
}

void pre() {
	For(i,0,N-1) C[i][0]=1;
	For(i,1,N-1) For(j,1,i) C[i][j]=(C[i-1][j]+C[i-1][j-1])%p;
	sort(b+1,b+m+1);
	For(i,1,m) b[i]+=b[i-1];
	For(B,0,m) For(A,0,n) 
		a[A]=max(a[A],b[m]-b[B]-(m-B)*(n-A)); //b[B]+a[A]+(m-B)*(n-A)>=b[m]
}

LL mo(LL x) { return x>=p?x-p:x; }

int main() {
	freopen("ip.in","r",stdin);
	freopen("ip.out","w",stdout);
	read(n); read(m);
	For(i,1,m) read(b[i]);
	pre();
	For(i,0,n) {
		if(a[i]>0) break;
 		f[i][0][0]=C[n][i];
	}
	For(i,0,n) {
		if(i) For(x,1,m) For(s,a[i],b[m]) For(j,1,i) {
			if(s-j*x+x<a[i-j+1]) break;
			if(s-j*x>=0) f[i][x][s]=mo(f[i][x][s]+f[i-j][x-1][s-j*x]*C[n-i+j][j]%p);		
		}
		For(s,a[i],b[m]) For(x,1,m) 
			f[i][x][s]=mo(f[i][x][s]+f[i][x-1][s]);
	}
	printf("%lld\n",f[n][m][b[m]]);
	Formylove;
}

你可能感兴趣的:(绍兴集训,动态规划,字符串,数论)