【BZOJ 3864】Hero meet devil

相关链接

题目传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=3864
神犇题解Ⅰ:http://www.cnblogs.com/clrs97/p/4799463.html
神犇题解Ⅱ:http://blog.csdn.net/popoqqq/article/details/46826271
神犇题解Ⅲ:http://blog.csdn.net/u011542204/article/details/51674319

解题报告

一开始一眼看成了“最长公共子串”
居然心里在嘲讽 $ WJMZBMR$ 也有naive的时候

先考虑一个这样的状态:$f[i][j][k]$ 表示文本串匹配到第$i$位、模式串匹配到第$j$位、最长公共子序列长度为$k$个时的方案数
但这样直接转移是错误的,因为这会使第三维的$k$意义变为:“最长公共子序列长度至少为$k$”。比如下面这个例子:

文本串:$azbzcz$
模式串:$abcd$
现在考虑在文本串末尾加入$d$
则$f[6][3][2]$会转移到$f[7][4][3]$去
但$f[7][4][3]$这个状态根本就是非法的

于是为了解决这个问题,我们不得不预处理出那些状态/转移是合法的
但我们发现这样的话,一般的预处理必须依赖文本串长成啥样,但这样做是爆炸的
于是考虑一个新的定义:$f[i][a_1][a_2]\ldots[a_n]$ 表示文本串到第$i$位时,文本串匹配到第$j$位,LIS的长度为$a_j$的方案数
这样的话,如果我们始终保持进行最优的转移,就不需要记录文本串的状态了
另外,我们还可以优化,因为$f[i][j] \le f[i][j+1] \le f[i][j]+1$,于是我们可以打成差分,然后压成二进制状态$S$
于是我们的状态就变成了$f[i][S]$,再预处理出转移数组 $trans[S][C]$ 表示状态为$S$,加入字符$C$之后应该到的状态
于是进行类似的转移就可以啦:$f[i+1][trans[S][C]] = f[i+1][trans[S][C]] + f[i][S]$

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

【Codeforces 747F】Igor and Interesting Numbers

相关链接

题目传送门:http://codeforces.com/contest/747/problem/F
优雅的暴力:http://codeforces.com/blog/entry/49171?#comment-331886
官方题解:http://codeforces.com/blog/entry/49171
神犇代码:http://codeforces.com/contest/747/submission/23125896

解题报告

首先这题推一推式子发现答案最多9位的样子
于是我们就可以暴力搜索,用Meet in Middle来做就好

当然这题有更加清真的、类似数位DP的做法
假定我们能够做到给定字符串长度和每个数字可以用多少次,求有多少种合法情况的话
显然可以像数位DP一样,从高位到低位逐位确定每一位的数字应该是多少

现在问题转化为如何求的合法情况。
依次考虑每一种数字,显然对答案的贡献只与有多少个空位有关
于是f[i][j]表示现在考虑到第i种数字,还剩j个空位的方案数
这样的话,暴力转移即可。
时间复杂度:O(16^3+16*9)

Code

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

int k,t,C[21][21],res[21];
char ori[]={'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'};

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 LL solve(int len, bool ty) {
	static LL f[2][20]; bool w,p;
	memset(f,0,sizeof(f));
	f[p=0][ty] = 1; w = 1; 
	for (int i=0;i<16;i++,w^=1,p^=1) {
		memset(f[w],0,sizeof(f[w]));
		for (int j=len;~j;j--) 
			for (int k=min(len-j,res[i]-(ty&(i==0)));~k;k--) 
				f[w][j+k] += f[p][j] * C[k][len-j];
	}
	return f[p][len];
}

int main() {
	for (int i=0;i<=20;i++) {
		C[0][i] = C[i][i] = 1;
		for (int j=1;j<i;j++) 
			C[j][i] = C[j-1][i-1] + C[j][i-1];
	}
	k = read(); t = read();
	for (int i=0;i<=15;i++) 
		res[i] = t;
	LL tmp; int len=1;
	while ((tmp = solve(len,0) - solve(len,1)) < k) 
		k -= tmp, len++;
	for (int i=len,ret=1;i;ret=0,i--) {
		res[ret]--;
		while ((tmp = solve(i-1,0)) < k) 
			res[ret++]++, res[ret]--, k -= tmp;
		printf("%c",ori[ret]);
	}
	return 0;
}

【BZOJ 1040】[ZJOI2008] 骑士

链接

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

题解

看到这个题,感觉除了网络流之外别无他法
结果这货是个基环森林啊!
I well vegetable are!

这个问题搬到树上去大家都会做
原题好像叫“没有上司的舞会”?
考虑给树加上一条边之后会发生什么变化:
这条非树边要么两段的点选一个,要么都不选
于是特殊处理一下,跑三遍树上DP就可以辣!

Code

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

const int N = 1000000+9;
const int M = N << 1;

int head[N],to[M],nxt[M];
int n,val[N],aim[N],tag[N],lb,rb;
LL vout,f[N][2];

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

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

void DFS(int w, int f) {
	tag[w] = 1; 
	for (int i=head[w];i;i=nxt[i]) if (to[i] != f && to[i]) {
		if (tag[to[i]]) {
			lb = w; rb = to[i];
			to[i] = to[i^1] = 0;
		} else {
			DFS(to[i], w);
		}
	}
}
 
void solve(int w, int fa) {
	f[w][0] = 0;
	f[w][1] = val[w];
	for (int i=head[w];i;i=nxt[i]) if (to[i] && to[i] != fa) {
		solve(to[i], w);
		f[w][1] += f[to[i]][0];
		f[w][0] += max(f[to[i]][1],f[to[i]][0]);
	}
	if (tag[w] == 2) {
		f[w][0] = 0;
	} else if (tag[w] == 3) {
		f[w][1] = 0;
	}
}
 
int main(){
	n = read();
	for (int i=1,t;i<=n;i++) {
		val[i] = read();
		if (aim[t=read()] != i) {
			Add_Edge(i, t);
			aim[i] = t;
		}
	}
	for (int i=1;i<=n;i++) {
		if (!tag[i]) {
			LL tmp; 
			lb = rb = 0;
			DFS(i,i);
			
			tag[lb] = 2; tag = 3;
			solve(i,i);
			tmp = max(f[i][0], f[i][1]);
			
			tag[lb] = 3; tag = 2;
			solve(i,i);
			tmp = max(tmp, max(f[i][0], f[i][1]));
			
			tag[lb] = 3; tag = 3;
			solve(i,i);
			tmp = max(tmp, max(f[i][0], f[i][1]));
			vout += tmp;
		}
	}
	printf("%lld\n",vout);
	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并记录一下转移就可以辣!

【BZOJ 2794】[POI2012] Cloakroom

链接

题目传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=2794
数据生成器:http://paste.ubuntu.com/23946550/
神犇题解:http://trinklee.blog.163.com/blog/static/23815806020149855817630

题解

这个题目假如没有A[]和B[]的限制的话,就是一个正常的背包
然而有了这两个东西,而且复杂度要求O(1)询问,这样我就不会了 QwQ

考虑传统背包的局限性:
1)添加物品的顺序没有限制,但实际上是浪费了一些性质
2)DP的是一个bool数组,只记录是否可以凑出,这样实际上也是浪费了信息

于是考虑离线,将询问的m和物品的a[]都按照升序排好序
然后规定f[i][j]表示用前i个物品,凑出j的b[]的最小值的最大值
之后对于每个询问,A[]可以用i来限制,B[]可以直接判断

于是边DP边回答询问的话
就可以O(1)处理所有询问辣!
★,°:.☆( ̄▽ ̄)/$:.°★ 。

Code

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

const int N = 1000+9;
const int M = 1000000+9;
const int INF = 100000;

struct Event{
	int a,b,c,d;
	inline Event() {}
	inline Event(int _a, int _b, int _c):a(_a),b(_b),c(_c) {}
	inline bool operator < (const Event &B) const {return a < B.a;}
}t[N],q[M];

int f[M],arr[M+N<<1],vout[M],tot,n,m,mx;

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,a,b,c;i<=n;i++) {
		a = read(); b = read(); c = read();
		arr[++tot] = c; arr[++tot] = b;
		t[i] = Event(b, c, a);
	}
	m = read();
	for (int i=1,a,b,c;i<=m;i++) {
		a = read(); b = read(); c = read() + a;
		arr[++tot] = a; arr[++tot] = c;
		q[i] = Event(a, c, b);
		q[i].d = i;
	}
	sort(arr+1, arr+1+tot);
	tot = unique(arr+1, arr+1+tot) - arr - 1;
	for (int i=1;i<=n;i++) {
		t[i].a = lower_bound(arr+1, arr+1+tot, t[i].a) - arr;
		t[i].b = lower_bound(arr+1, arr+1+tot, t[i].b) - arr;
		mx = max(mx, t[i].a);
	}
	for (int i=1;i<=m;i++) {
		q[i].a = lower_bound(arr+1, arr+1+tot, q[i].a) - arr;
		q[i].b = lower_bound(arr+1, arr+1+tot, q[i].b) - arr;
		q[i].a = min(q[i].a, mx);
	}
	sort(t+1, t+1+n);
	sort(q+1, q+1+m);
	f[0] = 1e9;
	for (int j=1,p1=1;j<=m;j++) {
		for (;t[p1].a<=q[j].a&&p1<=n;p1++) {
			for (int i=INF-t[p1].c,to=INF;i>=0;i--,to--) {
				f[to] = max(f[to], min(f[i], t[p1].b));	
			}
		}
		vout[q[j].d] = f[q[j].c] > q[j].b;
	}
	for (int i=1;i<=m;i++) {
		if (vout[i]) puts("TAK");
		else puts("NIE");
	}
	return 0;
}

【日常小测】噫

链接

题目传送门: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 10635】Prince and Princess

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

这题真的是好妙啊!
wv6v0iqq6vgq9xxczbs2c

首先O(n^4)的DP大家都会吧?
来说一说O(n*log(n))的做法:
将A串重新编号为1、2、3、4、5····
然后将编码的对应关系应用到B上面去
这样的话,就变成了求B的LIS了
于是搞一个BIT什么的就可以辣!

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

const int N = 250+9;
const int M = N * N;

int n,m,q,p,A[M],B[M],trans[M],vout[M];

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

namespace Fenwick_Tree{
	#define BIT Fenwick_Tree
	#define lowbit(x) ((x)&-(x))
	int MX[M];
	
	inline void modify(int pos, int val) {
		for (int i=pos;i<=m;i+=lowbit(i)) 
			MX[i] = max(MX[i], val);
	}
	
	inline int query(int pos) {
		int ret = 0;
		for (int i=pos;i;i-=lowbit(i)) 
			ret = max(ret, MX[i]);
		return ret;
	}
};

int main(){
	for (int k=1,T=read();k<=T;k++) {
		n = read(); m = n * n + 1;
		p = read() + 1; q = read() + 1;
		for (int i=1;i<=p;i++) A[i] = read();
		for (int i=1;i<=q;i++) B[i] = read();
		
		memset(trans,0,sizeof(trans));
		memset(vout,0,sizeof(vout));
		memset(BIT::MX,0,sizeof(BIT::MX));
		for (int i=1;i<=p;i++) trans[A[i]] = i;
		for (int i=1;i<=q;i++) B[i] = trans[B[i]];
		for (int i=1;i<=q;i++) if (B[i]) {
			vout[i] = BIT::query(B[i]) + 1;
			BIT::modify(B[i],vout[i]);
		}
		
		int ret = 0;
		for (int i=1;i<=q;i++) 
			ret = max(ret, vout[i]);
		printf("Case %d: %d\n",k,ret);
	} 
	return 0;
}

【算法笔记】数位DP

昨天以专题的形式再一次加强了数位DP
主要是以zky神犇的这一篇讲稿作为主线:
http://oi.cyo.ng/wp-content/uploads/2016/10/number_dp_by_zky.ppt

这一次最主要的收获就是学到了新的代码实现方式
之前一直是以预处理+询问的方式来写代码
这样的话,对于多组询问来讲,时间复杂度还不错
但代码实现复杂度简直没法接受
且对于部分题目,这样写起来会超级麻烦

新的写法无论实在时间复杂度还是编程复杂度上都很优越
以后应该会以这种代码作为主力辣!
给个参考:http://oi.cyo.ng/?p=1519

至于解题能力的话,似乎数位DP的要求不高?
不过能和AC自动机搞一起还是挺好玩哒:http://oi.cyo.ng/?p=1534

【BZOJ 1799】[Ahoi2009] self 同类分布

题目传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=1799

就是枚举模数然后搞一搞数位DP
%NanoApe,随便减一下枝就可以Rank1了:http://nanoape.is-programmer.com/posts/193348.html

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

int MOD,sta[20];
LL f[20][170][170];

LL DFS(int t, int sum, int mod, bool lim) {
	if (sum > MOD) return 0;
	else if (!t) {
		return sum == MOD && !mod;		
	} else if (!lim && ~f[t][sum][mod]) {
		return f[t][sum][mod];
	} else {
		LL ret = 0;
		for (int i=0,ty=(lim?sta[t]:9);i<=ty;i++) {
			ret += DFS(t-1, sum+i, (mod*10+i)%MOD, lim&&i==sta[t]);
		}
		return lim? ret: f[t][sum][mod] = ret;
	}
}

inline LL cal(LL lim) {
	int cnt = 0;
	while (lim) {
		sta[++cnt] = lim % 10;
		lim /= 10;
	}
	if (cnt*9 < MOD) return 0;
	else return DFS(cnt,0,0,1);
}

int main(){
	LL a,b,vout=0; cin>>a>>b;
	for (int i=1;i<=162;i++) {
		memset(f,-1,sizeof(f));
		MOD = i;
		vout += cal(b);
		vout -= cal(a-1);
	}
	printf("%lld\n",vout);
	return 0;
}

【HDU 3709】Balanced Number

题目传送门:http://acm.hdu.edu.cn/showproblem.php?pid=3709
中文题面:http://blog.csdn.net/to_be_better/article/details/50731499

就是枚举一下平衡点,然后dp一下
另外……
我似乎真的爱上这种递归的写法了…..
7`XEKOC~R{6W$L~J1UPP9MX

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

int sta[20];
LL f[20][20][1500];

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

LL DFS(int t, int crt, int val, bool ruf) {
	if (val < 0) return 0;
	else if (!t) {
		return !val;
	} else if (~f[t][crt][val] && !ruf) {
		return f[t][crt][val];
	} else {
		LL ret = 0;
		for (int i=0,lim=ruf?sta[t]:9;i<=lim;i++) {
			ret += DFS(t-1,crt,val+(t-crt)*i,ruf&&i==sta[t]);
		}
		return ruf? ret: f[t][crt][val] = ret;
	}
}

inline LL cal(LL lim) {
	int cnt = 0;
	while (lim) {
		sta[++cnt] = lim % 10;
		lim /= 10;
	}
	LL ret = 0;
	for (int i=1;i<=cnt;i++) {
		ret += DFS(cnt,i,0,1);
	}
	return ret - cnt + 1;
}

int main(){
	memset(f,-1,sizeof(f));
	for (int T=read();T;--T) {
		LL l = read(), r = read();
		printf("%I64d\n",cal(r)-((l>0)?cal(l-1):0));
	}
	return 0;
}