【Codeforces 559E】Gerald and Path

相关链接

题目传送门:http://codeforces.com/contest/559/problem/E
官方题解:http://codeforces.com/blog/entry/19237
中文题面:http://blog.csdn.net/mengzhengnan/article/details/47057557

解题报告

这题官方题解是$O(n^4)$的
然而评论区给出了$O(n^3)$甚至$O(n^2)$的算法
不过评论区并没有给题解,只是扔了两份非常不可读的代码
于是我一个都没有懂 QwQ
后来自己想了一个$O(n^3)$的DP,来说一说吧!

考虑最常规的$DP$式子$f[i][j]$表示用了前$i$个路灯,已被照亮的区间的最右端点为$j$
这样的话,只有一种情况会出现问题:

ps:红色的部分会被漏掉
于是我们就预处理出所有路灯向左照的时候覆盖情况
因为超过第i个路灯的位置并且对答案有贡献的路灯最多一个
所以我们就可以枚举是那个路灯超过了i
这样的话,新增转移多了$n^2$个
又因为每一个转移会被使用$O(n)$次,于是总的时间复杂度为:$O(n^3)$

Code

#include<bits/stdc++.h>
#define LL long long
using namespace std;

const int N = 300+9;

int n,tot,Hash[N],f[N][N];
struct Mat{int p,l;}m[N];
struct Trans{int l,r,mx;};
vector<Trans> t[N]; 

inline int read() {
	char c=getchar(); int f=1,ret=0;
	while (c<'0'||c>'9') {if(c=='-')f=-1;c=getchar();}
	while (c<='9'&&c>='0') {ret=ret*10+c-'0';c=getchar();}
	return ret * f;
}

inline int Val(int p, int l, int r) {
	if (r <= p) return 0;
	if (l >= p) return Hash[r] - Hash[l];
	return Hash[r] - Hash[p];
}

int main() {
	n = read(); 
	for (int i=1,p,l;i<=n;i++) {
		p = read(); l = read();
		Hash[++tot] = p;
		Hash[++tot] = p + l;
		Hash[++tot] = p - l;
		m[i] = (Mat){p,l};
	}
	auto cmp = [](const Mat &A, const Mat &B) {return A.p < B.p;};
	sort(m+1, m+1+n, cmp);
	sort(Hash+1, Hash+1+tot);
	tot = unique(Hash+1, Hash+1+tot) - Hash - 1;
	for (int i=1,r,l;i<=n;i++) {
		r = m[i].p; l = m[i].p-m[i].l;
		for (int j=1,cr,cl;j<i;j++) {
			if (l > m[j].p) continue;
			cr = max(r, m[j].p+m[j].l); cl = l;
			for (int k=j+1;k<i;k++) 
				cl = min(cl, m[k].p-m[k].l);
			cl = lower_bound(Hash+1, Hash+1+tot, cl) - Hash;
			cr = lower_bound(Hash+1, Hash+1+tot, cr) - Hash;
			t[j].push_back((Trans){cl,cr,i});
		}
		l = lower_bound(Hash+1, Hash+1+tot, l) - Hash;
		r = lower_bound(Hash+1, Hash+1+tot, r) - Hash;
		t[i].push_back((Trans){l,r,i});
		l = m[i].p; r = m[i].p + m[i].l;
		l = lower_bound(Hash+1, Hash+1+tot, l) - Hash;
		r = lower_bound(Hash+1, Hash+1+tot, r) - Hash;
		t[i].push_back((Trans){l,r,i});
	}
	for (int i=1;i<=n;i++) {
		for (int j=1;j<=tot;j++) 
			f[i][j] = max(f[i][j], f[i-1][j]);
		for (int k=t[i].size()-1,l,r,mx;~k;k--) {
			l = t[i][k].l; r = t[i][k].r; mx = t[i][k].mx;
			for (int j=1;j<=tot;j++) {
				if (j >= r) break;
				f[mx+1][r] = max(f[mx+1][r], f[i][j] + Val(j, l, r));
			}
		}
	}
	int vout = 0;
	for (int i=1;i<=n+1;i++) {
		for (int j=1;j<=tot;j++) {
			vout = max(vout, f[i][j]);
		}
	}
	printf("%d\n",vout);
	return 0;
}

【BZOJ 3992】[SDOI2015] 序列统计

相关链接

题目传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=3992
Latex题面:http://paste.ubuntu.com/23997878/
数据生成器:http://paste.ubuntu.com/24006205/
神犇题解:http://www.cnblogs.com/gromah/p/4431016.html
NTT相关:http://blog.miskcoo.com/2015/04/polynomial-multiplication-and-fast-fourier-transform#i-13

解题报告

这题$O(m^3logn)$的基于矩阵快速幂的算法大家都会吧?
然而只能通过 $60\% $ 的数据 QwQ

然后就需要一点黑科技了
考虑模数这么奇怪,于是可能是用元根来搞一搞
然后我们发现,我们可以用元根的幂次来表示 $0 \sim m – 1$ 的每一个数
于是我们就可以把乘法换成幂次的加法,于是就可以搞NTT了!
于是用NTT来搞生成函数,复杂度:$O(nmlogm)$
然后再套上一开始讲的快速幂,那么就是$O(mlogmlogn)$的复杂度啦!

Code

话说这似乎是第一次撸$NTT$呢!
还有一点小激动啊!
不过这一份代码封装太多了,好慢啊 QwQ

#include<bits/stdc++.h>
#define LL long long
using namespace std;

const int N = 9000;
const int M = N << 2;
const int MOD = 1004535809;

int l,n,m,x,pos[N];

inline int read() {
	char c=getchar(); int f=1,ret=0;
	while (c<'0'||c>'9') {if(c=='-')f=-1;c=getchar();}
	while (c<='9'&&c>='0') {ret=ret*10+c-'0';c=getchar();}
	return ret * f;
}

inline int Pow(int w, int t, int mod) {
	int ret = 1;
	for (;t;t>>=1,w=(LL)w*w%mod)
		if (t & 1) ret = (LL)ret * w % mod;
	return ret;
}

inline int Get_Primitive_Root(int w) {
	vector<int> pri; int cur = w - 1;
	for (int i=2,lim=ceil(sqrt(w));i<=cur&&i<=lim;i++) {
		if (cur % i == 0) {
			pri.push_back(i);
			while (cur % i == 0) cur /= i;
		}
	}
	if (cur > 1) pri.push_back(cur);
	if (pri.empty()) return 2;  
	for (int i=2;;i++) {
		for (int j=pri.size()-1;j>=0;j--) {
			if (Pow(i, w/pri[j], w) == 1) break;
			if (!j) return i;
		}
	}
}

struct Sequence{
	int a[M],POS[M],len;
	inline Sequence() {}
	inline Sequence(int l) {
		memset(a,0,sizeof(a));
		len = l; a[0] = 1;
	}
	inline Sequence(Sequence &B) {
		memcpy(this, &B, sizeof(B));
		len=B.len;
	}
	inline void NTT(int f) {
		static const int G = Get_Primitive_Root(MOD);
		int l = -1; for (int i=len;i;i>>=1) l++;
		if (!POS[1]) {
			for (int i=1;i<len;i++) { 
				POS[i] = POS[i>>1]>>1;
				if (i & 1) POS[i] |= 1 << l-1;
			} 
		}
		for (int i=0;i<len;i++) if (POS[i] > i) 
			swap(a[i], a[POS[i]]);
		for (int seg=2;seg<=len;seg<<=1) {
			int wn = Pow(G, MOD/seg, MOD);
			if (f == -1) wn = Pow(wn, MOD-2, MOD);
			for (int i=0;i<len;i+=seg) {
				for (int k=i,w=1;k<i+seg/2;k++,w=(LL)w*wn%MOD) {
					int tmp = (LL)w * a[k+seg/2] % MOD;
					a[k+seg/2] = ((a[k] - tmp) % MOD + MOD) % MOD;
					a[k] = (a[k] + tmp) % MOD;
				}
			}
		}
		if (f == -1) { 
			for (int i=0,rev=Pow(len,MOD-2,MOD);i<len;i++) 
				a[i] = (LL)a[i] * rev % MOD;
		}
	}
	inline void Multiply(Sequence B) {
		NTT(1); B.NTT(1);
		for (int i=0;i<len;i++)
			a[i] = (LL)a[i] * B.a[i] % MOD;
		NTT(-1); 
		for (int i=m-1;i<len;i++) 
			(a[i-m+1] += a[i]) %= MOD, a[i] = 0;
	} 
	inline void pow(int t) {
		Sequence ret(len),w(*this);
		for (;t;t>>=1,w.Multiply(w)) 
			if (t & 1) ret.Multiply(w);
		memcpy(this, &ret, sizeof(ret));
	}
}arr;

int main() {
	l = read(); m = read();
	x = read() % m; n = read();
	int PRT = Get_Primitive_Root(m);
	for (int cur=1,i=0;i<m-1;i++,cur=cur*PRT%m) pos[cur] = i;
	for (int i=0,t;i<n;i++) t=read(), t?arr.a[pos[t%m]]++:0;
	int len = 1; while (len < m) len <<= 1;
	arr.len = len << 1; arr.pow(l); 
	printf("%d\n",(arr.a[pos[x]]+MOD)%MOD);
	return 0;
}

【BZOJ 4584】[APIO2016] 赛艇

相关链接

题目传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=4584
数据生成器:http://paste.ubuntu.com/23985797/
神犇题解:http://blog.csdn.net/worldwide_d/article/details/54572626

解题报告

这货如果划艇数量比较科学的话,我们大家都会做$O(n^3)$的$DP$
于是很自然地想到离散化
离散化之后的区间之间的大小很好比较,但同一个区间内的大小不怎么好比较
但仔细想一想,如果我们知道一个离散化之后的区间内有几所学校,那么似乎搞一搞组合数就可以了
于是定义$f[i][j][k]$表示考虑到第$i$个学校,派出数量最多的学校的数量在离散化区间$j$,这个区间内共有$k$个学校的方案数
然后$O(n^3)$的DP就可以啦!

另外,这题要卡常
不要问我是怎么知道的 _(:з」∠)_
似乎用FFT优化的话就不用卡常了?
我们还是安心卡常吧

再另外的话,这题的Discuss灰常赛艇啊!

Code

#include<bits/stdc++.h>
#define LL long long
using namespace std;

const int N = 500+9;
const int M = 1000+9;
const int MOD = 1000000007;

int n,tot,hash[M],f[M][N],h[M][N],g[M],delta[M];
int len[M],l[N],r[N],rev_len[M],rev_n[N],cnt[M]; 

inline int read() {
	char c=getchar(); int f=1,ret=0;
	while (c<'0'||c>'9') {if(c=='-')f=-1;c=getchar();}
	while (c<='9'&&c>='0') {ret=ret*10+c-'0';c=getchar();}
	return ret * f;
}

inline int Pow(int w, int t) {
	int ret = 1;
	for (;t;t>>=1,w=(LL)w*w%MOD)
		if (t & 1) ret = (LL)ret * w % MOD;
	return ret;
}

int main() {
	n = read(); 
	for (int i=1;i<=n;i++) {
		l[i] = hash[++tot] = read();
		r[i] = hash[++tot] = read();
		rev_n[i] = Pow(i, MOD - 2);
	}
	sort(hash+1, hash+1+tot);
	tot = unique(hash+1, hash+1+tot) - hash - 1;
	for (int i=1;i<=n;i++) {
		l[i] = lower_bound(hash+1, hash+1+tot, l[i]) - hash;
		r[i] = lower_bound(hash+1, hash+1+tot, r[i]) - hash;
	}
	for (int i=1;i<=tot;i++) {
		len[i] = hash[i] - hash[i-1];
		rev_len[i] = Pow(len[i], MOD - 2);
	}
	for (int i=1,tmp;i<=n;i++) {
		memcpy(h,f,sizeof(f));
		memset(delta,0,sizeof(delta));
		(f[l[i]][min(n,len[l[i]])] += g[l[i]-1] + 1) %= MOD;
		(delta[l[i]] += g[l[i]-1] + 1) %= MOD;
		for (int k=2,j=l[i],lim=min(n,len[j]),bas=len[j]-1,t=min(n,len[j]);k<=lim;k++,bas--) {
			tmp = ((LL)h[j][k-1] * bas % MOD) * (LL)rev_len[j] % MOD;
			f[j][t] = (f[j][t] + tmp) % MOD;
			delta[j] = (delta[j] + tmp) % MOD;
		}
		for (int j=l[i]+1,LIM=r[i],*a=f[j],*b=h[j];j<=LIM;++j,a=f[j],b=h[j]) {
			(a[1] += (LL)len[j] * (g[j-1] + 1) % MOD) %= MOD; cnt[j]++;
			(delta[j] += (LL)len[j] * (g[j-1] + 1) % MOD) %= MOD;
			for (int k=2,lim=min(cnt[j],len[j]),bas=len[j]-1;k<=lim;k++,bas--) { 
				tmp = ((LL)b[k-1] * bas % MOD) * (LL)rev_n[k] % MOD;
				a[k] = (a[k] + tmp) % MOD;
				delta[j] = (delta[j] + tmp) % MOD;
			}
		}
		for (int i=1,cur=0;i<=tot;i++) {
			cur = (cur + delta[i]) % MOD;
			g[i] = (g[i] + cur) % MOD;
		}
	}	
	printf("%d\n",g[tot]);
	return 0;
}

【BZOJ 1442】[POI2006] Crystal

相关链接

题目传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=1442
中文题解:http://www.shuizilong.com/house/archives/poi-xiii-stage-3-problem-crystals/

解题报告

首先我们定义“折越”:

这一位上可以选1,但选了0

如果我们从高位到低位考虑的话,如果有一位发生了折越,那之后的东西就可以随便取了
于是我们考虑在每一种方案第一次发生折越的时候将其统计进入答案
对每个数我们分情况讨论:

  1. 第i位只能填0
    那么剩下的位数的可能情况就有a & (1<<i)种辣
  2. 第i位可以发生折越
    那么不发生折越的情况同第一类
    发生折越之后,因为可以随便取,所以得乘上1<<i

这样的话,我们类似数位DP一样,从高位到低位依次DP就好
另外,此题没有取模的操作,需要用unsigned long long才能通过本题

Code

#include<bits/stdc++.h>
#define LL unsigned long long
using namespace std;

const int N = 59;

LL n,arr[N],vout;

int main() {
	cin>>n;
	for (int i=1;i<=n;i++) cin>>arr[i];
	for (int t=31;~t;t--) {
		LL f[3],t1,t2,od; od = 0;  
		f[0] = 1; f[1] = f[2] = 0;
		for (int i=1,tmp;i<=n;i++) {
			tmp = (arr[i] & ((1LL<<t) - 1)) + 1;
			if (arr[i] & (1LL<<t)) {
				od ^= 1;
				t1 = f[0] + (f[2] << t);
				t2 = f[1] << t; 
				f[0] *= tmp;
				(f[1] *= tmp) += t1;
				(f[2] *= tmp) += t2;
			} else {
				f[0] *= tmp;
 				f[1] *= tmp;
				f[2] *= tmp;
			}
		}
		if (od) {vout += f[1] - 1; break;}
		else vout += f[2];
	}
	cout<<vout;
	return 0;
}

【BZOJ 4543】[POI2014] Hotel加强版

相关链接

题目传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=4543
神犇题解Ⅰ:http://blog.csdn.net/u010600261/article/details/53576251
神犇题解Ⅱ:http://blog.csdn.net/neither_nor/article/details/51278993

解题报告

设$f[i][j]$为到点$i$距离为$j$的点的个数
设$g[i][j]$表示一堆点到点$i$,各自距离均为$j$的对数
这样的话,我们就可以愉快地DP了,时间复杂度$O(n^2)$

现在将这颗树树链剖分,将重儿子重定义为子树深度最深的点
明显一个点的重儿子到这个点的转移可以直接将数组平移,这个用指针实现是$O(1)$的
现在考虑轻儿子,用轻儿子的数组去更新父亲节点的信息

这样看起来是$O(n^2)$的,但实际上这是$O(n)$的
考虑内存使用了多少:每一条重链的空间等于重链长度,所以空间复杂度为$O(n)$的
考虑每一个申请的空间,只会被用来更新所在重链的最上面那个点的父亲
也就是说每一个数组的单位,只会被访问一次,所以时间复杂度也是$O(n)$的啦!

另外值得一提的是,这种优化方式的普适性较强
除了这道题之外,我还在四道题里见到过这样的优化
比如:UOJ 284

【BZOJ 4380】[POI2015] Myjnie

相关链接

题目传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=4380
神犇题解Ⅰ:http://www.cnblogs.com/clrs97/p/5271139.html
神犇题解Ⅱ:http://www.cnblogs.com/DaD3zZ-Beyonder/p/6052067.html

解题报告

首先不难发现第$i$个人的贡献只与 $\min ({p_j}|j \in [{a_i},{b_i}])$ 有关
于是我们考虑记录 $f(l,r,mn)$ 表示处理完区间 $[l,r]$ 的所有人,且区间最小值为 $mn$ 的总花费是多少
然后怎么进行转移,我就没有想出来了,总想着要把一个单调栈给搞到状态里 QwQ
看了题解以后,真的是只差一点点啊!

现在来说一说正解。设$g(i,j)$表示覆盖第$i$个门店,$c_k \ge j$的人有多少个
我们枚举区间最小值的位置$pos$,那么 $f(l,r,mn) = \max (f(l,pos – 1,x) + f(pos + 1,r,y) + g(pos,mn) \times mn|x,y \ge mn)$
于是我们记录一个$f$的后缀最小值什么的,就可以在 $O({n^3}m)$ 的时间复杂度内解决这个问题啦!
至于打印方案,我们记录一下每个状态的来源,然后逆向DFS回去输出就可以啦!

【BZOJ 4145】[AMPPZ2014] The Prices

相关链接

题目传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=4145
神犇题解:http://blog.csdn.net/popoqqq/article/details/46547683

解题报告

先来说一个自己YY的暴力
枚举每一个合法集合,考虑这个集合的物品全部在某一个商店购买
如果我们把这些集合再拿来搞一搞组合,显然可以搞出来正确答案
使用背包DP的话,直接做复杂度是$O(4^m)$,考虑一点优化:
根据我们对于集合的定义,两个集合能够组合在一起,当且仅当两个集合没有交集
然后写个程序跑一跑,发现这种组合大概只会有4e7个,于是暴力搞一搞就好啦!
然后就垫底了 QwQ

现在考虑正解
刚刚我们其实是把整个商店买了哪些物品当作一个新物品,然后用新物品来进行背包DP
考虑我们直接把商店里面的物品拿来做背包DP会有什么问题:商店本身的花费$d_i$不好整
于是我们分阶段来背包DP,每一个商店一个阶段。
在这个阶段中,我们给所有状态加上这个商店的花费$d_i$,表示我们钦定这个商店要买东西
然后把物品拿来做背包,最后再和上个阶段的对应状态取个$min$
然后就做完啦!时间复杂度: $O(nm{2^m})$

Code

贴的这份代码是我YY的暴力

#include<bits/stdc++.h>
#define LL long long
using namespace std;

const int N = 100+9;
const int M = 65536;

int n,m,cost[N],f[M];
pair<int,int> que[M];

inline int read() {
	char c=getchar(); int f=1,ret=0;
	while (c<'0'||c>'9') {if(c=='-')f=-1;c=getchar();}
	while (c<='9'&&c>='0') {ret=ret*10+c-'0';c=getchar();}
	return ret * f;
}

void DFS(int w, int sta, int c) {
	if (w == m + 1) {
		f[sta] = min(f[sta], c);
	} else {
		DFS(w+1, sta, c);
		DFS(w+1, sta|(1<<w-1), c+cost[w]);
	}
}

void update(int t, int sta, int w) {
	if (t == m + 1) {
		f[sta|w] = min(f[sta|w], f[sta] + f[w]);
	} else {
		update(t+1, sta, w);
		if ((sta&(1<<(t-1))) == 0)
			update(t+1, sta, w|(1<<(t-1)));
	}
}

int main() {
	memset(f,60,sizeof(f));
	n = read(); m = read();
	for (int i=1,d;i<=n;i++) {
		d = read();
		for (int j=1;j<=m;j++) 
			cost[j] = read();
		DFS(1, 0, d);
	}
	for (int i=1,lim=1<<m;i<lim;i++)
		que[i] = make_pair(__builtin_popcount(i), i);
	sort(que+1, que+(1<<m));
	for (int i=1,lim=1<<m;i<lim;i++) 
		update(1, que[i].second, 0);
	printf("%d\n",f[(1<<m)-1]);
	return 0;
}

—————————— UPD 2017.2.7 ——————————
感觉使用以下代码来枚举子集可能会使我的暴力不那么接近于TLE:

for (int i=s;i;i=(i-1)&s) {}

【BZOJ 4753】[JSOI2016] 最佳团体

相关链接

题目传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=4753
神犇题解Ⅰ:http://blog.csdn.net/a_crazy_czy/article/details/51761871
神犇题解Ⅱ:http://blog.csdn.net/WorldWide_D/article/details/51565773

解题报告

考虑分数规划的套路的话,先二分一个值
现在问题变成了:在树上选一些点,使得点权和大于等于0
但这题还有一个奇怪的要求:一个点被选,其所有祖先必须被选

这就需要一个姿势非常奇怪的DP:

先将树搞到DFS序上去,设 $r(i)$ 表示第i个点的子树中DFS序最大的那个点
设 $f(i,j)$ 表示处理到了第$i$个点,已经选择了$j$个点
那么转移分两种:

  1. 选择第i+1个点:$f(i+1,j+1) = f(i+1,j+1) + f(i,j) + w_{i+1}$
  2. 不选择第i+1个点:$f(r(i+1)+1,j) = f(r(i+1)+1,j) + f(i,j)$

这样相当于如果不选择第$i$个点的话,就强行跳过他的子树
妙,真是太妙了!

【AtCoder】[Grand Contest 009 C] Division into Two

相关链接

题目传送门:http://agc009.contest.atcoder.jp/tasks/agc009_c
数据生成器:http://paste.ubuntu.com/23868295/
官方题解:https://agc009.contest.atcoder.jp/editorial

解题报告

这个题目,乍一看似乎需要 $O(n^3)$ 的DP才能完成
但如果我们仔细观察一下合法划分的特点:

连续一段给X集合,连续一段给Y集合,如此往复

于是我们可以设 $f[i][0/1]$ 表示考虑完前$i$个位置,第$i$个元素给了 $x/y$,第$i+1$个元素的集合与第$i$个元素不同
这样的话,我们只需要在转移的时候把不满足要求的情况给去掉就可以啦!
时间复杂度:$O(n)$

Code

#include<bits/stdc++.h>
#define LL long long
using namespace std;

const int N = 100000+9;
const int MOD = 1000000007;
const LL INF = 4e18;

LL A,B,LIM[2],s[N];
int n,f[2][N],sum[2][N],lst[2][N],pre[2][N];

inline LL read() {
	char c=getchar(); LL f=1,ret=0;
	while (c<'0'||c>'9') {if(c=='-')f=-1;c=getchar();}
	while (c<='9'&&c>='0') {ret=ret*10+c-'0';c=getchar();}
	return ret * f;
}

int main() {
	n = read(); A = LIM[0] = read(); B = LIM[1] = read();
	for (int i=1,l1=1,l2=1,p1=0,p2=0;i<=n;i++) {
		s[i] = read();
		while (l1 < i && s[i] - s[l1+1] >= A) l1++; lst[0][i] = l1;
		while (l2 < i && s[i] - s[l2+1] >= B) l2++; lst[1][i] = l2;
		if (i > 1 && s[i] - s[i-1] < A) p1 = i - 1; pre[0][i] = p1;
		if (i > 1 && s[i] - s[i-1] < B) p2 = i - 1; pre[1][i] = p2; 
	}
	lst[0][n+1] = lst[1][n+1] = n; 
	s[n+1] = INF; s[0] = -INF;
	for (int i=1,l,r;i<=n;i++) {
		for (int j=0;j<=1;j++) {
			r = min(i-1, lst[j^1][i+1]);
			if (s[i+1] - s[lst[j^1][i+1]] >= LIM[j^1]) 
				if (l = pre[j][i], l <= r) f[j][i] = (sum[j^1][r] - sum[j^1][l-1]) % MOD;
			if (s[pre[j][i]+1] - s[pre[j][i]] >= LIM[j]) f[j][i]++;
			sum[j][i] = (sum[j][i-1] + f[j][i]) % MOD;
		}
	}
	printf("%d\n",((f[0][n]+f[1][n])%MOD+MOD)%MOD);
	return 0;
}

【BZOJ 3861】Tree

相关链接

题目传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=3861
数据生成器:http://paste.ubuntu.com/23863232/
神犇题解:http://www.cnblogs.com/clrs97/p/6329731.html

解题报告

因为每一个数会且仅会出现在一个集合中
于是我们就可以很方便地容斥辣!
时间复杂度:$O(n^2)$

Claris还给出了一个方法:

将可以匹配的集合与点连边,这样的话,就搞出一个二分图
于是答案就是统计这个二分图完备匹配的个数

因为这个二分图性质很特殊,于是也是可以DP哒!
时间复杂度:$O(n^2)$

Code

#include<bits/stdc++.h>
#define LL long long
using namespace std;

const int N = 1009;
const int MOD = 1000000007;

int n,cnt[N],f[2][N],POW[N];

inline int read() {
	char c=getchar(); int f=1,ret=0;
	while (c<'0'||c>'9') {if(c=='-')f=-1;c=getchar();}
	while (c<='9'&&c>='0') {ret=ret*10+c-'0';c=getchar();}
	return ret * f;
}

inline int Pow(int w, int t) {
	int ret = 1;
	while (t) {
		if (t & 1) ret = (LL)ret * w % MOD;
		w = (LL)w * w % MOD; t >>= 1;	
	}
	return ret;
}

int main() {
	POW[0] = 1;
	for (int i=1;i<N;i++) POW[i] = (LL)POW[i-1] * i % MOD;
	for (int tag=0,tot=1,spj=0;n=read();tag=spj=0,++tot) {
		for (int i=1;i<=n;i++) {
			cnt[i] = read();
			if (cnt[i] == n) tag = 1;
			if (!cnt[i]) spj++;
			for (int j=1;j<=cnt[i];j++) 
				read();
		}
		if (tag) {printf("Case #%d: 0\n",tot); continue;}
		int p = 1, w = 0, vout = 0; 
		memset(f[p],0,sizeof(f[p]));
		f[p][0] = 1;
		for (int i=1;i<=n;++i,p^=1,w^=1) {
			memset(f[w],0,sizeof(f[w]));
			f[w][0] = f[p][0];
			for (int j=1;j<=n;j++) {
				(f[w][j] += f[p][j]) %= MOD;
				(f[w][j] += (LL)f[p][j-1] * cnt[i] % MOD) %= MOD;
			}
		}
		for (int i=0,t=1;i<=n;i++,t*=-1) {
			f[p][i] = (LL)f[p][i] * POW[n-i] % MOD;
			(vout += f[p][i] * t) %= MOD;
		}
		vout = (LL)vout * Pow(POW[spj], MOD-2) % MOD;
		printf("Case #%d: %d\n",tot,(vout+MOD)%MOD);
	}
	return 0;
}

【BZOJ 4455】[ZJOI2016] 小星星

相关链接

题目传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=4455
神犇题解:http://www.cnblogs.com/DaD3zZ-Beyonder/p/5793086.html

解题报告

这题这个阶乘级别的状压DP大家都会吧?

$f(i,j,S)$ 表示树上第$i$个点,对应图中第$j$个点,子树中的对应状态状压后为$S$的方案数

然后我发现了两件事:
1. 这样会T
2. 我不会更优的算法了

正解的话,用了一个非常神奇的DP
考虑枚举有图中有哪些点被映射,然后 $O(n^3)$ 就可以求出满足边的限制的方案数(但点不一定是一一对应的)
然后我们加上点数刚好的情况,减去点数差一的情况,加上相差为2的情况…
于是我们就可以在 $O(2^n \cdot n^3)$ 的时间复杂度内解决这个问题啦!

【BZOJ 4664】Count

相关链接

题目传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=4664
神犇题解:http://blog.csdn.net/ta201314/article/details/52506505

解题报告

如果这题不会,可以先去看看【BZOJ 4498】魔法的碰撞
然后你会发现这题简直就是大原题!

我们发现只要我们定了左右两个数的大小关系
于是这个数对于答案的贡献就确定了
于是我们像【BZOJ 4498】魔法的碰撞里一样,讨论一下大小关系的所有情况就可以啦!

【BZOJ 4498】魔法的碰撞

相关链接

题目传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=4498
神犇题解Ⅰ:http://blog.csdn.net/visit_world/article/details/51090964
神犇题解Ⅱ:http://www.cnblogs.com/wangyurzee7/p/5365710.html

解题报告

如果我们知道了一个排列的最短长度 $w$
那么我们这个序列对于答案的贡献就是 $C_{l – w}^n$
那么如果我们知道最短长度为$w$的排列有$c_w$个,那么答案就是 $\sum\limits_{w = 1}^l {{c_w} \cdot C_{l – w}^n} $

那么现在的问题就是如何求$c_w$了
考虑一个Mogician,其对于答案的贡献有$3$种:$0/d_i/2d_i$
考虑按权值从大到小安排,我们钦定一个Mogician的贡献

如果贡献为$0$,那么两侧都是比他大的,换一句话来说他两侧不能再安排其他膜法师了,于是能安排膜法师的位置减少一个
如果贡献为$d_i$,那么一侧比他大,一侧比他小,能安排膜法师的位置没有变化
如果贡献为$2d_i$,那么两侧都应该比他小,于是能安排膜法师的位置增加一个

这样的话,我们就可以设$f[i][j][k]$表示“考虑到第$i$个膜法师,能安排膜法师的位置有$j$个,当前序列的最短长度为$k$”
然后就可以愉快的转移啦!时间复杂度: $O({n^4} + L)$

—————————— UPD 2017.3.3 ——————————
今天又做到一道类似的题目:BZOJ 4321
题解的话,可以看这里:http://blog.csdn.net/geotcbrl/article/details/49663401

【BZOJ 4006】[JLOI2015] 管道连接

相关链接

题目传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=4006
神犇题解:http://www.cnblogs.com/lidaxin/p/5301180.html
斯坦纳树相关:http://endlesscount.blog.163.com/blog/static/821197872012525113427573/

解题报告

带关键点的最小生成树

好像在梦里见过的样子……
今天看到这个题居然想了很久这应该怎么做……

就是搞一个斯坦纳树就可以啦!
就是状压DP用SPFA的方式来更新

【BZOJ 3930】[CQOI2015] 选数

相关链接

题目传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=3930
神犇题解:http://www.cnblogs.com/iwtwiioi/p/4986316.html

解题报告

这题,仔细想一想,岂不是跟这个题一样:http://oi.cyo.ng/?p=498
于是答案就是这个东西:$ \sum\limits_{i = 1}^m {\sum\limits_{j = 1}^m { \cdots \sum\limits_{k = 1}^m {[gcd(i,j, \cdots ,k) = K]} } } = \sum\limits_{i = 1}^{\left\lfloor {\frac{m}{K}} \right\rfloor } {\sum\limits_{j = 1}^{\left\lfloor {\frac{m}{K}} \right\rfloor } { \cdots \sum\limits_{k = 1}^{\left\lfloor {\frac{m}{K}} \right\rfloor } {[gcd(i,j, \cdots ,k) = 1]} } } = \sum\limits_{d = 1}^{\left\lfloor {\frac{m}{K}} \right\rfloor } {\mu (d) \cdot {{\left\lfloor {\frac{m}{{Kd}}} \right\rfloor }^n}}$
于是剩下的锅就是预处理莫比乌斯函数的前缀和了!
然而我只会 $ O(n)$ 的做法,于是就跪了

PoPoQQQ大爷给了一种神奇的做法:http://blog.csdn.net/popoqqq/article/details/44917831
大概就是说,莫比乌斯函数的前缀和可以进行如下变换:
$ \sum\limits_{i = 1}^n {\mu (i) = 1 – \sum\limits_{i = 2}^n {(0 – \mu (i)) = } 1 – \sum\limits_{i = 2}^n {(\sum\limits_{j|i} {\mu (j)} – \mu (i))} = 1 – \sum\limits_{i = 2}^n {\sum\limits_{j|i,i \ne j} {\mu (j)} } } = 1 – \sum\limits_{j = 1}^n {\mu (j) \cdot (\left\lfloor {\frac{n}{j}} \right\rfloor – 1)} = 1 – \sum\limits_{j = 1}^{\left\lfloor {\frac{n}{2}} \right\rfloor } {\mu (j) \cdot (\left\lfloor {\frac{n}{j}} \right\rfloor – 1)}$
换一句话说,莫比乌斯函数的前缀和可以做到 $ O(\sqrt n \log n)$
这样的话,感觉复杂度是 $ O(n \log n)$ 的
然而PoPoQQQ大爷说,预处理前 $ S$ 个值的话,复杂度就可以变成:$ O(\frac{n}{S}\sqrt n lo{g_2}\frac{n}{S})$
然而并不知道怎么证明,感觉很有道理的样子!

当然这题还有容斥的做法
首先有结论:$ gcd(i,j) \le (r – l),(i,j \in (l,r))$
证明的话,参见这里:https://blog.sengxian.com/solutions/bzoj-3930
于是考虑 $ f(i)$ 表示gcd为i的方案数,则 $ f(i) = {\left\lfloor {\frac{m}{{Ki}}} \right\rfloor ^n} – \sum\limits_{i|j} {f(j)}$
这样的话, $ f(1)$ 就是答案辣!

—————————— UPD 2017.2.20 ——————————
突然发现,莫比乌斯函数的前缀和不是杜教筛的模板题吗?
当然PoPoQQQ的做法也有可取之处:不用复杂度公式推导

【BZOJ 4621】Tc605

相关链接

题目传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=4621
神犇题解:http://m.blog.csdn.net/article/details?id=52107091

解题报告

想了两个小时,确定是 $ O({n^2})$ 的DP,但就是推不出来式子 QwQ

观察最终的序列可以发现其有一下性质

  1. 数的相对位置与原本的位置相同
  2. 一种数字一定是连续的一段

更近一步,不难发现:满足上述条件的式子一定可以通过合法的操作构造出来
于是我们就可以DP了:$ f(i,j)$ 表示用了i个位置,进行了j次操作的方案数
对于原序列上的每一个数,枚举所有合法的存在区间 $ (l,r)$ 并进行转移$ f(r,j) + = f(l – 1,j – 1)$
于是就可以 $ O(n^3)$ DP辣!