从重要的题开始
题解连接
题意:
求长度为n的字符串,最多有多少个子串x,满足L<=x<=R, |L|<=|R|<= 800 , n <= 2000 , 都是10进制表示
这是一道数位dp好题!感谢fdf大佬的指教。fdf的题解
我们把所有子串的贡献放在它第一次脱离限制的地方统计。而脱离限制可以枚举l或者r的前缀再枚举下一维统计。这里我们需要固定该串的长度,因为脱离限制后任意填,只要长度合法即可
如果[l] < |r| - 1,还需要统计长度介于两者之间的串。
把所有脱离限制的串插入AC自动机(记录每个状态所有脱离限制的合法长度),则只要匹配到该节点,并且长度合法即可贡献答案。并且所有fail树的祖先作为该状态的后缀也可以贡献答案。
f(i,u)表示长度为i,匹配到AC自动机上u,的最优答案。转移的时候预处理g(x,i)表示节点x,还剩长度i可以贡献的答案数。
因为要求字典序最小,所以记忆化搜索,逐位确定最优解
代码分类讨论写得很长,但是注释还是比较清晰的
总结:
L == R的特判打错了,调了1个小时。这样的错误真的必须仔仔细细的把每个代码的细节看清楚,不能借助对拍!
为什么还是对拍出错就知道哪里错了,而自己看代码却查不出来。必须改正自己调题的恶习,必须静心把每个地方的错误看出来!代码真的要一句一句读!
刻骨铭心的教训啊!
#include
using namespace std;
#define rep(i,l,r) for(register int i = l ; i <= r ; i++)
#define repd(i,r,l) for(register int i = r ; i >= l ; i--)
#define rvc(i,S) for(register int i = 0 ; i < (int)S.size() ; i++)
#define rvcd(i,S) for(register int i = ((int)S.size()) - 1 ; i >= 0 ; i--)
#define fore(i,x)for (register int i = head[x] ; i ; i = e[i].next)
#define forup(i,l,r) for (register int i = l ; i <= r ; i += lowbit(i))
#define fordown(i,id) for (register int i = id ; i ; i -= lowbit(i))
#define pb push_back
#define prev prev_
#define stack stack_
#define mp make_pair
#define fi first
#define se second
#define lowbit(x) (x&(-x))
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
typedef pair<int,char> pr;
const ll inf = 2e18;
const int N = 2e4 + 10;
const int maxn = 2020;
const ll mod = 1e9 + 7;
char s1[maxn],s2[maxn];
int a[maxn],b[maxn];
int lenl,lenr,n;
int f[maxn][N],nxt[N][10],fail[N],g[N][maxn],trans[N][10],tot;
void init(){
int cur = 0;
rep(i,1,lenl) a[i] = s1[i] - '0';
rep(i,1,lenr) b[i] = s2[i] - '0';
if ( lenl < lenr ){ //上、下界长度不同分开考虑
cur = 0;
rep(i,1,lenl){
rep(j,a[i] + 1,9){ //枚举脱离限制的地方。自由节点固定长度。每个子串在刚好脱离限制的时候统计答案、因为只要长度够一定贡献
if ( !nxt[cur][j] ) nxt[cur][j] = ++tot;
int x = nxt[cur][j];
g[x][lenl - i]++;
}
if ( !nxt[cur][a[i]] ) nxt[cur][a[i]] = ++tot;
cur = nxt[cur][a[i]];
}
g[cur][0]++;//刚好顶满下界也合法
cur = 0;
rep(i,1,lenr){
rep(j,0,b[i] - 1){
if ( i == 1 && !j ) continue; //小于上界不能有前导0
if ( !nxt[cur][j] ) nxt[cur][j] = ++tot;
int x = nxt[cur][j];
g[x][lenr - i]++;
}
if ( !nxt[cur][b[i]] ) nxt[cur][b[i]] = ++tot;
cur = nxt[cur][b[i]];
}
g[cur][0]++;
if ( lenr > lenl + 1 ){ //长度介于上下界只要无前导0即可
cur = 0;
rep(i,1,9){
int &x = nxt[cur][i];
if ( !x ) x = ++tot;
rep(j,lenl + 1,lenr - 1) g[x][j - 1]++; //j - 1为还剩的长度
}
}
}
else{ //上下界长度相同,看什么时候分离
int id = lenl + 1;
rep(i,1,lenl){
if ( a[i] != b[i] ){
rep(j,a[i] + 1,b[i] - 1){ //刚好分离的时候上下界同时考虑
if ( !nxt[cur][j] ) nxt[cur][j] = ++tot;
int x = nxt[cur][j];
g[x][lenl - i]++;
}
id = i + 1;
break;
}
if ( !nxt[cur][b[i]] ) nxt[cur][b[i]] = ++tot;
cur = nxt[cur][b[i]];
}
if ( id > lenl ){ //在最后一位分离要特判
if ( a[lenl] == b[lenl] ){ //上下界完全相同,只有一种情况贡献答案
// if ( !nxt[cur][b[lenl]] ) nxt[cur][b[lenl]] = ++tot;
// cur = nxt[cur][b[lenl]];
//这个时候cur已经匹配到最后一位了!
g[cur][0]++;
}
else{ //上下界最后一位不同,恰好顶满分别统计,如果没有顶前面已经统计
if ( !nxt[cur][b[lenl]] ) nxt[cur][b[lenl]] = ++tot;
int x = nxt[cur][b[lenl]];
g[x][0]++;
if ( !nxt[cur][a[lenl]] ) nxt[cur][a[lenl]] = ++tot;
x = nxt[cur][a[lenl]];
g[x][0]++;
}
}
else{
int y = cur;
if ( !nxt[cur][a[id - 1]] ) nxt[cur][a[id - 1]] = ++tot;
cur = nxt[cur][a[id - 1]];
rep(i,id,lenl){
rep(j,a[i] + 1,9){
if ( !nxt[cur][j] ) nxt[cur][j] = ++tot;
int x = nxt[cur][j];
g[x][lenl - i]++;
}
if ( !nxt[cur][a[i]] ) nxt[cur][a[i]] = ++tot;
cur = nxt[cur][a[i]];
}
g[cur][0]++;
cur = y;
if ( !nxt[cur][b[id - 1]] ) nxt[cur][b[id - 1]] = ++tot;
cur = nxt[cur][b[id - 1]];
rep(i,id,lenr){
rep(j,0,b[i] - 1){
if ( !nxt[cur][j] ) nxt[cur][j] = ++tot;
int x = nxt[cur][j];
g[x][lenr - i]++;
}
if ( !nxt[cur][b[i]] ) nxt[cur][b[i]] = ++tot;
cur = nxt[cur][b[i]];
}
g[cur][0]++;
}
}
}
int q[N],hh,tt;
void print_trie(){
int x = 0;
tt = hh = 0;
q[tt++] = x;
while ( hh < tt ){
int x = q[hh++];
cout<<x<<" : "<<endl;
rep(i,0,9){
if ( nxt[x][i] ){
q[tt++] = nxt[x][i];
cout<<i<<" "<<nxt[x][i]<<endl;
}
}
cout<<endl;
}
}
void build(){
rep(i,0,9) if ( nxt[0][i] ) q[tt++] = nxt[0][i];
while ( hh < tt ){
int x = q[hh++];
rep(i,0,9){
if ( nxt[x][i] ){
int y = nxt[x][i] , p = fail[x];
while ( p && !nxt[p][i] ) p = fail[p];
fail[y] = nxt[p][i];
q[tt++] = y;
}
}
}
rep(i,0,tt - 1){ //所有fail树上的祖先恰好不重不漏的统计了匹配到当前状态,还剩长度i,以当前节点为后缀的子串贡献次数(只统计在这里脱离限制的子串
int x = q[i] , y = fail[x]; //进队顺序祖先一定先被计算
rep(j,1,n) g[x][j] += g[x][j - 1];
rep(j,0,n){
g[x][j] += g[y][j];
}
}
// print_trie();
rep(x,0,tot){ //更新trans不能漏掉0号节点
rep(j,0,9){
int p = x;
while ( p && !nxt[p][j] ) p = fail[p];
trans[x][j] = nxt[p][j];
}
}
}
pr from[maxn][N];
bool vis[maxn][N];
int DP(int n,int m){ //用记忆化搜索,直接求出最小字典序。保证从前往后转移最优
if ( !n ) return 0;
if ( vis[n][m] ) return f[n][m];
vis[n][m] = 1; int &d = f[n][m];
rep(i,0,9){
int to = trans[m][i];
int cur = DP(n - 1,to) + g[to][n - 1];
if ( cur > d ){
d = cur;
from[n][m] = mp(to,i);
}
}
return d;
}
void solve(){
printf("%d\n",DP(n,0));
int cur = 0;
rep(i,1,n){
a[i] = from[n - i + 1][cur].se;
cur = from[n - i + 1][cur].fi;
}
/* memset(f,-1,sizeof(f));
f[0][0] = 0;
rep(i,0,n - 1){
rep(j,0,tot){
if ( f[i][j] == -1 ) continue;
rep(k,0,9){
int to = trans[j][k] , len = n - i - 1;
int d = f[i][j] + g[to][len];
if ( f[i + 1][to] < d ) f[i + 1][to] = d , pre[i + 1][to] = mp(j,k);
}
}
}
int ans = 0 , rec = 0;
rep(i,0,tot){
if ( f[n][i] > ans ){
ans = f[n][i] , rec = i;
}
}
printf("%d\n",ans);
repd(i,n,1){
a[i] = pre[i][rec].se;
rec = pre[i][rec].fi;
}*/
rep(i,1,n) printf("%d",a[i]);
puts("");
}
int main(){
freopen("input.txt","r",stdin);
scanf("%s %s %d",s1 + 1,s2 + 1,&n);
lenl = strlen(s1 + 1) , lenr = strlen(s2 + 1);
init();
build();
solve();
}
题意:
给出一棵树,上面有白点和未染色点,白色先手,轮流染色。当染成3个连续白点获胜。问是平局还是白胜。 n <= 5e5
这道贪心很好!
首先通过加点把白点转化成无色。加点只要保证进行相同的染色后先后手不变,并且状态和以前一致。
这样转化大大减少了分类讨论的情况。即使不转化也能讨论。但是转化后模型简单很多!
接着进行特殊情况分类讨论。
考场上想得太马虎,漏掉了骨头型加中间点为奇数的情况(当时只讨论了中间一个点,没想到扩展还可以)
在确认自己算法的时候要更加仔细,当然这样的思维要多加锻炼才行!
my code
题意:给出按dfs序标号的树,询问一个点离区间[l,r]中最近叶子的距离
这道题直接做因为不知道lca在哪里,很难维护、再加上dfs序给定,无法进行点分和重链剖分。
此时考虑离线(本来是听显然的思路,考场上问了wxh才想到,很不应该)
直接维护每个点到所有叶子的距离,统一处理询问即可
题意:可以对序列c[i]进行操作: 选 取 位 置 i , ( 2 < = i < = n − 1 ) , 将 c [ i ] 变 成 c [ i + 1 ] + c [ i − 1 ] − c [ i ] 选取位置i,(2<=i<=n - 1),将c[i] 变成 c[i + 1] + c[i - 1] - c[i] 选取位置i,(2<=i<=n−1),将c[i]变成c[i+1]+c[i−1]−c[i]
问是否能将c[i]变成b[i]
每次操作想当于把差分数组交换位置,问题变成比较差分序列排序后是否相同和第一个元素是否相同
显然,如果上述相同,原序列是相同的。并且最后一个元素 c[n] = sigma(d[i]) + c[1] , 不随差分数组的顺序改变,不用特判。
判断序列相同的思路经常有转化为差分或者前缀和判断!
题意:给出n个数字,可以用来组成两种三元组(i,i + 1,i + 2) , (i,i,i)问最多多少个三元组
一开始看到1e6以为是贪心。其实应该简单的dp(问了队友)
因为3个以上重复的顺子直接用3个三元组代替,所以顺子个数%3记录即可。f(i,0…2,0…2) 表示以i开头,i在中间的顺子个数。简单转移即可。注意舍掉非法状态
看似非常简单的模型,有时用贪心,有时用dp处理。积累的太少,要学会总结和融会贯通、找性质!