【BZOJ 4282】慎二的随机数列

相关链接

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

解题报告

首先我们需要得到一个结论:一定存在一组最优解,使得所有的模糊的位置均在LIS中
现在我们来证明一下这个结论:

考虑如果有一组最优解中存在一个模糊的位置不在最优解中
那么讲这个模糊的位置换成后面的第一个确定位置的数即可,这样不会使答案变劣

于是我们就假设一个确定的位置的前缀中有a个不确定位置
那么我们把该确定位置的值减去a(相当于把那a个不确定位置在值域上留出位置)
然后对确定的串做一次LIS,然后加上不确定位置的个数即可

Code

我写LIS一直用的BIT,但PoPoQQQ大爷的LIS似乎更加优雅
他是直接维护一个最优的LIS,然后每一次lower_bound就可以了!

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

const int N = 200000+9;

int n,m,tot,vout,_hash[N],f[N],arr[N];
char pat[10];

class Fenwick_Tree{
	int mx[N];
	public:
		inline void modify(int p, int v) {
			for (int i=p;i<=tot;i+=lowbit(i))
				mx[i] = max(mx[i], v);
		}
		inline int query(int p) {
			int ret = 0; 
			for (int i=p-1;i;i-=lowbit(i))
				ret = max(ret, mx[i]);
			return ret;
		}
	private:
		inline int lowbit(int x) {
			return x & (-x);
		}
}BIT;

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;
}

int main() {
	n = read();
	for (int i=1;i<=n;++i) {
		scanf("%s",pat+1);
		if (pat[1] == 'K') {
			arr[++tot] = read() - m;
			_hash[tot] = arr[tot];		
		} else ++m;
	}
	n = tot; sort(_hash+1, _hash+1+tot);
	tot = unique(_hash+1, _hash+1+tot) - _hash - 1;
	for (int i=1;i<=n;i++) {
		arr[i] = lower_bound(_hash+1, _hash+1+tot, arr[i]) - _hash;
		f[i] = BIT.query(arr[i]) + 1;
		vout = max(f[i], vout);
		BIT.modify(arr[i], f[i]);
	} 
	printf("%d\n",vout + m);
	return 0;
}

【BZOJ 4416】[SHOI2013] 阶乘字符串

相关链接

题目传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=4416
神犇题解Ⅰ:http://blog.csdn.net/flaze_/article/details/52607165
神犇题解Ⅱ:http://309459245.lofter.com/post/1dd7269e_a37f28d

解题报告

第一次做这么神奇的判定性题目
感觉非常神奇啊!神清气爽!

闲来无事写了一份beamer,大家随便看看吧
另外,这货的证明是错的:http://blog.csdn.net/horizon_smz/article/details/50905084
slide

Code

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

const int N = (1<<21) + 9;
const int M = 500;
const int SGZ = 21;

int n,m,f[N],last[SGZ],nxt[M][21];
char pat[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;
}

int main() {
	for (int T=read();T;T--) {
		m = read();
		scanf("%s",pat+1);
		n = strlen(pat+1);
		if (m > 21) {
			puts("NO");
			continue;
		}
		for (int i=0;i<m;i++) 
			last[i] = nxt[n+1][i] = n + 1;
		for (int i=n;~i;i--) {
			for (int j=0;j<m;j++) 
				nxt[i][j] = last[j];
			if (i) last[pat[i]-'a'] = i;
		}
		for (int i=1,lim=1<<m;i<lim;i++) {
			f[i] = 0;
			for (int j=0;j<m;j++) {
				if (i & (1 << j)) {
					f[i] = max(f[i], nxt[f[i^(1<<j)]][j]);
				}
			}	
		}
		if (f[(1<<m)-1] <= n) puts("YES");
		else puts("NO");
	}
	return 0;
}

【BZOJ 3997】[TJOI2015] 组合数学

相关链接

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

解题报告

这题又™是结论题
需要用到的结论:

Dilworth定理:DAG的最小链覆盖=最大点独立集

类似的结论还有:

最大团=补图上的最小点覆盖的补集
最大反链的大小=最小链划分
最大链的大小=最小反链划分

知道结论之后,实际上就是让你求一些点,相互不能到达,且点权和最大
这样的话,从左下到右上搞一个DP就可以了!

Code

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

const int N = 1000+9;

int n,m,val[N][N],f[N][N],g[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;
}

int main() {
	for (int T=read();T;T--) {
		memset(f,0,sizeof(f));
		memset(g,0,sizeof(g));
		m = read(); n = read();
		for (int j=1;j<=m;j++) {
			for (int i=1;i<=n;i++) {
				val[i][j] = read();
			}
		}
		for (int i=1;i<=n;i++) {
			for (int j=m,cur=0;j;j--) {
				f[i][j] = cur + val[i][j];
				cur = max(cur, g[j]);
				g[j] = max(g[j], f[i][j]);
			}
		}
		int vout = 0;
		for (int i=1;i<=m;i++) 
			vout = max(vout, g[i]);
		printf("%d\n",vout);
	}
	return 0;
}

【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 4374】Little Elephant and Boxes

相关链接

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

解题报告

这题如果钱的总数小一点就可以直接DP了
于是我就想到了这个题:BZOJ 2749
于是我们就可以把钱给离散化到900以内啦!
但这样我们没有办法进行转移,然后就不会了 QwQ

不过万能的Claris告诉我们,物品只有30个
于是我们可以折半暴搜,这样的话就不需要转移了
直接枚举左侧的值,右侧搞一个指针跟着动一动就好
时间复杂度: $O(nm{2^{\frac{n}{2}}} + n{m^2})$

【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)$ 的时间复杂度内解决这个问题啦!