【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 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

【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 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 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辣!

【AtCoder】[Regular Contest 067 F] Yakiniku Restaurants

相关链接

题目传送门:http://arc067.contest.atcoder.jp/
官方题解:https://arc067.contest.atcoder.jp/editorial

解题报告

因为区间的选择会同时影响所有颜色
所以肯定不可能按颜色来分开计算

考虑枚举右端点,左端点从左向右扫
每一种餐票的最终贡献一定是单调不增的
于是使用单调队列维护这个东西
在转折点的地方打个差分
因为差分的值不受颜色类型的影响,所以不分颜色
于是直接暴力扫左端点就可以了
时间复杂度:$ O({n^2})$

Code

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

const int N = 5000+9;
const int M = 200+9;

int n,m,que[M][N],cnt[M],mat[M][N],bst[M];
LL delta[N],x[N],vout,cur,dis,len;

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(); m = read();
	for (int i=1;i<n;i++) 
		x[i] = read();
	for (int j=1;j<=n;j++) {
		for (int i=1;i<=m;i++) {
			mat[i][j] = read();
		}
	}
	for (int r=1;r<=n;r++) {
		cur = 0; len = (dis += x[r-1]);
		for (int i=1;i<=m;i++) {
			while (cnt[i] && mat[i][que[i][cnt[i]]] <= mat[i][r]) {
				if (cnt[i] > 1) 
					delta[que[i][cnt[i]-1]] += mat[i][que[i][cnt[i]-1]] - mat[i][que[i][cnt[i]]];
				cnt[i]--;
			}
			que[i][++cnt[i]] = r;
			if (cnt[i] > 1) delta[que[i][cnt[i]-1]] += mat[i][r] - mat[i][que[i][cnt[i]-1]];
			bst[i] = max(bst[i], mat[i][r]);
			cur += bst[i];
		}
		for (int l=1;l<=r;l++) {
			vout = max(vout, cur - len);
			cur += delta[l];
			len -= x[l];
		}
	}
	printf("%lld\n",vout);
	return 0;
}

【Codeforces 722E】Research Rover

相关链接

题目传送门:http://codeforces.com/contest/722/problem/E
官方题解:http://codeforces.com/blog/entry/47497
中文题面:http://blog.csdn.net/aufeas/article/details/52939314

解题报告

这题在考虑如何去重的方面真的是好神啊!

定义:

$ h(i,j)$ 表示点i走到点j的方案数
$ f(i)$ 表示从起点,不经过特殊点,走到点i的方案数
$ g(i,j)$ 表示从点i出发,经过j个特殊点走到终点的方案数
$ val(i)$ 表示经过i个特殊点后剩余的值

现在考虑如何计算这四个量:
1. $ h(i,j) = C_{\Delta (x) + \Delta (y)}^{\Delta (x)}$
2. $ f(i) = h(start,i) – \sum {f(j) \cdot h(j,i)}$
3. $ g(i,j) = h(i,end) – \sum\limits_p {g(p,j) \cdot h(p,i)} – \sum\limits_{p = 1}^{j – 1} {g(i,p)}$
4. $ val(i)$ 随便预处理就好

现在考虑如何使用这四个量计算答案:
$ ans = \sum\limits_{i = 1}^k {f(i) \cdot \sum\limits_{j = 0}^{{{\log }_{2(s)}}} {g(i,j) \cdot val(j)} }$
于是我们就可以在 $ O({n^2}lo{g_2}(s))$ 的时间复杂度内解决这个问题啦!

Code

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

const int N = 2000+9;
const int M = 200000+9;
const int MOD = 1000000007;
const int L = 25;

int n,m,tot,cnt,K,S,tag,POW[M],REV[M];
int ans,val[L],vout[L],g[N][L],f[N];
struct Point{
	int x,y;
	inline bool operator < (const Point &B) const {
		return x < B.x || (x == B.x && y < B.y);
	}	
	inline bool operator <= (const Point &B) {
		return x <= B.x && y <= B.y;
	}
}p[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;
}

inline void prework() {
	for (int w=S;w>1;w=ceil(w*0.5)) 
		val[tot++] = w;
	val[tot] = 1;
	POW[0] = REV[0] = 1;
	for (int i=1;i<M;i++)
		POW[i] = (LL)POW[i-1] * i % MOD;
	REV[M-1] = Pow(POW[M-1], MOD-2);
	for (int i=M-2;i;i--)
		REV[i] = (LL)REV[i+1] * (i+1) % MOD;
}

inline int Path(int a, int b) {
	if (p[a].x > p[b].x || p[a].y > p[b].y) return 0;
	else {
		int x = p[b].x - p[a].x;
		int y = p[b].y - p[a].y + x;
		return ((LL)POW[y] * REV[x] % MOD) * REV[y-x] % MOD;
	}
}

int main() {
	n = read(); m = read();
	K = read(); S = read();
	prework();
	for (int i=1,x,y;i<=K;i++) {
		x = read(); y = read();
		if (x == 1 && y == 1) tag++;
		else if (x == n && y == m) tag++;
		else p[++cnt] = (Point){x, y}; 
	}
	sort(p+1, p+1+cnt);
	p[cnt+1] = (Point){n,m};
	p[0] = (Point){1,1};
	for (int i=cnt;i;i--) {
		for (int j=0;j<=tot;j++) {
			g[i][j] = Path(i, cnt+1);
			for (int k=i+1;k<=cnt&&j<tot;k++) {
				if (p[i] <= p[k]) {
					(g[i][j] -= (LL)g[k][j] * Path(i, k) % MOD) %= MOD;
				}
			}
			for (int k=0;k<j;k++)
				(g[i][j] -= g[i][k]) %= MOD;
		}
	}
	for (int i=1;i<=cnt;i++) {
		f[i] = Path(0, i);
		for (int j=1;j<i;j++) {
			if (p[j] <= p[i]) {
				(f[i] -= (LL)f[j] * Path(j, i) % MOD) %= MOD;
			}
		}
		for (int j=0;j<=tot;j++) 
			(vout[min(tot,j+1)] += (LL)f[i] * g[i][j] % MOD) %= MOD;
	}
	vout[0] = Path(0, cnt+1);
	for (int j=1;j<=tot;j++)
		(vout[0] -= vout[j]) %= MOD;
	for (int j=tag,tmp;j;j--) {
		tmp = vout[tot];
		for (int i=tot;i;i--) 
			vout[i] = vout[i-1];
		vout[0] = 0;
		vout[tot] = (vout[tot] + tmp) % MOD;
	}
	for (int i=0;i<=tot;i++)
		(ans += (LL)vout[i] * val[i] % MOD) %= MOD;
	ans = (LL)ans * Pow(Path(0, cnt+1), MOD-2) % MOD;
	printf("%d\n",(ans+MOD)%MOD);
	return 0;
}

【BZOJ 1025】[SCOI2009] 游戏

链接

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

解题报告

这题把数学模型抽象出来就是
取一些数ai,要求∑ai<=n
求这些数的最小公倍数有多少种不同的可能

我自己就只想到这里就想不出来了…
果然还是得膜拜神犇…

逆向考虑:考虑一个数能否被凑出来
假如将其质因数分解之后为:p1^c1 * p2 ^ c2 * …… * px^cx
并且∑pi^ci <= n,那么一定可以凑出来
考虑最小公倍数的定义:至少有一个数中包含了pi^ci
所以这样判断是没有问题的

于是从小到大考虑质因数,使用分组背包进行转移
f[i][j]表示已经考虑了前i个质数,\(\sum\limits_{k = 1}^i {p_k^{{c_k}}} = j\)
时间复杂度O(n^2)

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

const int N = 1000+9;

LL n,tot,pri[N],vout,f[N][N];

inline int read(){
	char c=getchar(); int ret=0,f=1;
	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 void prework() {
	for (int i=2;i<=n;i++) {
		bool tag = 1;
		for (int j=2;j<i;j++) {
			if (i % j == 0) {
				tag = 0;
				break;
			}
		}
		if (tag) {
			pri[++tot] = i;
		}
	}
	f[0][0] = 1;
}

int main(){
	n = read();
	prework();
	for (int i=1;i<=tot;i++) {
		for (int j=0;j<=n;j++) {
			f[i][j] += f[i-1][j];
			for (int k=pri[i];k<=j;k*=pri[i]) {
				f[i][j] += f[i-1][j-k];
			}
		}
	}
	for (int i=0;i<=n;i++) 
		vout += f[tot][i];
	printf("%lld\n",vout);
	return 0;
}

【BZOJ 3749】[POI2015] Łasuchy

链接

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

题解

我看到这题第一眼想到的是贪心
后来又想到了先搞一个方案然后进行调整
最后看了题解才知道这货居然是个DP!
之前见过构造题用线性基来搞的,居然还有直接上DP的 QwQ
i4uxe_lmpcmhcqym45

考虑第i个人的决策,显然只与他左右二人的决策相关
于是从左向右考虑每一个人的决策
于是搞一个O(4*n)的DP并记录一下转移就可以辣!

【日常小测】噫

链接

题目传送门:http://oi.cyo.ng/wp-content/uploads/2016/11/20161112.pdf (T2)
原版题解:http://oi.cyo.ng/?attachment_id=1904

题解

首先这个题目暴力的DP大家肯定都会
然而并不知道小火车是怎么优化到O(n^3)的…..
WV6V0IQQ@`6VGQ9XXCZBS2C

考虑暴力DP的限制:同时记录了最大值和最小值
但因为有d的限制,所以只要记录一个极值便可以推出另外一个

但直接扔掉一个极值并且按照原有的转移来转移的话,会丢掉一些情况
比如:从下到上,节点的权值递增的情况
于是考虑所有点在更新答案的时候,更新所有符合限制的上限
于是转移就可以做到O(1)辣!
接着,整体DP就成了O(n^2)辣!

代码

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

const int N = 5000+9;
const int M = N << 1;
const int MX = 5000;
const int INF = 100000000;

int head[N],nxt[M],to[M],val[N],f[N][N],g[N],n,d;

inline int read(){
	char c=getchar(); int ret=0,f=1;
	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 void Add_Edge(int u, int v) {
	static int T = 0;
	to[++T] = v; nxt[T] = head[u]; head[u] = T;
	to[++T] = u; nxt[T] = head[v]; head[v] = T;
}

void solve(int w, int fa) {
	int lim = min(val[w]+d, MX);
	for (int i=val[w];i<=lim;i++)
		f[w][i] = 0;
	for (int i=head[w];i;i=nxt[i]) {
		if (to[i] != fa) {
			solve(to[i], w);
			for (int j=val[w];j<=lim;j++) 
				f[w][j] += min(g[to[i]], f[to[i]][j]);
		}
	}	
	for (int i=val[w];i<=lim;i++)
		g[w] = min(g[w], f[w][i] + 1);
}

int main(){
	freopen("yi.in","r",stdin);
	freopen("yi.out","w",stdout);
	memset(f,60,sizeof(f));
	memset(g,60,sizeof(g));
	d = read(); n = read();
	for (int i=1;i<=n;i++)
		val[i] = read();
	for (int i=1;i<n;i++)
		Add_Edge(read(), read());
	solve(1, 0);
	printf("%d\n",g[1]);
	return 0;
}

【Codevs 4560】[NOIP2015] 子串

题目传送门:http://codevs.cn/problem/4560/

就是一个很简单的DP加一个滚动数组的优化
然而为什么纸张的我去年连这是个DP都看不出来 QAQ

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

const int MOD = 1000000007; 

int f[2][209][209][2],w,p,n,m,K;
char s1[1009],s2[1009];

inline int read(){
	char c=getchar(); int ret=0,f=1;
	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(); m = read(); K = read();
	scanf("%s%s",s1+1,s2+1);
	w = 1; f[p][0][0][0] = 1;
	
	for (int t=1;t<=n;t++,w^=1,p^=1) {
		memset(f[w],0,sizeof(f[w]));
		for (int i=0;i<=m;i++) {
			for (int k=0;k<=K;k++) {
				if (f[p][i][k][0]) {
					(f[w][i][k][0] += f[p][i][k][0]) %= MOD;
					if (s1[t] == s2[i+1]) {
						(f[w][i+1][k+1][1] += f[p][i][k][0]) %= MOD;
					}
				}
				if (f[p][i][k][1]) {
					(f[w][i][k][0] += f[p][i][k][1]) %= MOD;
					if (s1[t] == s2[i+1]) {
						(f[w][i+1][k][1] += f[p][i][k][1]) %= MOD;
						(f[w][i+1][k+1][1] += f[p][i][k][1]) %= MOD;
					}
				}
			}
		}
	}
	printf("%d\n",(f[p][m][K][0]+f[p][m][K][1])%MOD);
	return 0;
}

【Codeforces 626F】Group Projects

相关链接

题目传送门:http://codeforces.com/problemset/problem/626/F
中文题面:http://h0rnet.hatenablog.com/entry/2016/11/12/CFR_626_F__Group_Projects_(DP)

解题报告

考虑把每个人放到值域上
于是问题转化为搞一些线段,使得总长度之和小于K

考虑每一个节点的决策:
1. 成为线段左端点
2. 成为线段右端点
3. 放到某个线段中间/自己一组

于是我们就有了一个$DP$:
$f[i][j][k]$表示已经处理完前i个数,还有j各线段左端点未配对,线段总长为k的方案数
转移如上文所述,是$O(3)$的
于是复杂度为$O(k*n^2)$

Code

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

const int MOD = 1000000007;

int f[2][200+9][1000+9],n,arr[1000+9],w,p,k,cnt[500+9],tot,que[1000+9];

inline int read(){
	char c=getchar(); int ret=0,f=1;
	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(); k = read(); w = 1, p = 0;
	for (int i=1;i<=n;i++) cnt[arr[i]=read()]++;
	for (int i=1;i<=500;i++) {
		if (cnt[i]) {
			que[++tot] = i; 
			cnt[tot] = cnt[i];
		}
	}
	que[0] = que[1];
	
	f[p][0][k] = 1;
	for (int i=1,delta;i<=tot;i++) {
		memset(f[w],0,sizeof(f[w]));
		delta = que[i] - que[i-1];
		for (int j=0;j<=n;j++) {
			for (int t=j*delta;t<=k;t++) {
				f[w][j][t-j*delta] += f[p][j][t];
			}
		}
		swap(w, p);
		
		for (int x=1;x<=cnt[i];x++,w^=1,p^=1) {
			memset(f[w],0,sizeof(f[w]));	
			for (int j=0;j<=n;j++) {
				for (int t=0;t<=k;t++) {
					if (f[p][j][t]) {
						LL tmp = f[p][j][t];
						(f[w][j][t] += tmp * (j + 1) % MOD) %= MOD;
						if (j) (f[w][j-1][t] += tmp * j % MOD) %= MOD;
						(f[w][j+1][t] += tmp) %= MOD;				
					}
				}
			}
		}
	}
	int ret = 0;
	for (int i=0;i<=k;i++) 
		ret = (ret + f[p][0][i]) % MOD;
	printf("%d\n",ret);
	return 0;
}

【UVa 1027】Toll

题目传送门:https://uva.onlinejudge.org/index.php?problem=3468
中文题面:《算法竞赛·入门经典·训练指南》P331

一直以为是一个建图非常神奇的最短路/网络流
然而居然是类似斯坦纳树一样的转移怪异的DP
qrrxcieey4qta4clrvl

设d[i]为满足题目条件的情况下,到达第i个点时的最少货物量
然后反向各更新即可

更新的方式的话,显然SPFA的正确性没有问题
又因为没有负权,所以Dijkstra也是正确的?

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

const int N = 70;
const int M = 2000+9;
const double EPS = 1e-4;

int head[N],nxt[M],to[M],m,T,MN,s,t,case_cnt;
LL d[N]; bool inq[N]; queue<int> que; 

inline int read(){
	char c=getchar(); int ret=0,f=1;
	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 ID(const char c) {
	if ('a' <= c && c <= 'z') return c - 'a' + 1;
	else return c - 'A' + 27;
}

inline void Add_Edge(int u, int v) {
	to[++T] = v; nxt[T] = head[u]; head[u] = T;
	to[++T] = u; nxt[T] = head[v]; head[v] = T;
}

inline LL cost(int w, LL val) {
	if (w == s) return 0;
	else if (w <= 26) return 1;
	else {
		LL l=1,r=1e15,ret=1,mid;
		while (l <= r) {
			mid = l + r >> 1;
			if (val+mid-(LL)(ceil((val+mid)/20.0)+EPS) >= val) ret = mid, r = mid - 1;
			else l = mid+1;
		}
		return ret;
	}
}	

inline LL SPFA() {
	memset(d,0x3f,sizeof(d));
	d[t] = MN + (s!=t?cost(t, MN):0); 
	inq[t] = 1; que.push(t);
	
	while (!que.empty()) {
		int w = que.front(); inq[w] = 0; que.pop();
		for (int i=head[w];i;i=nxt[i]) {
			if (d[to[i]] > d[w] + cost(to[i],d[w])) {
				d[to[i]] = d[w] + cost(to[i],d[w]);
				if (!inq[to[i]]) inq[to[i]] = 1, que.push(to[i]);
			}
		}
	}
	return d[s];
}

int main(){
	static char U[10],V[10];
	for (m=read();~m;m=read(),T=0) {
		memset(head,0,sizeof(head));
		
		for (int i=1;i<=m;i++) {
			scanf("%s %s",U+1,V+1);
			Add_Edge(ID(U[1]), ID(V[1]));
		}
		
		MN = read();
		scanf("%s %s",U+1,V+1);
		s = ID(U[1]); t = ID(V[1]);
		printf("Case %d: %lld\n",++case_cnt,SPFA());
	}
	return 0;
}