hdu5542 The Battle of Chibi
题意:
给长度为n的数组a,和一个整数m
要求计算数组中长度为m的子序列数量,满足子序列严格递增
答案对1e9+7取模
数据范围:n,m<=1e3
解法:
因为n和m只有1e3,则:
令d[i][j]表示弟以i结尾长度为j的上升子序列数量
容易想到转移方程为:d[i][j]=sigma(d[k][j-1]),其中1<=k<i,且a[k]<a[i]
枚举i,j是O(n^2)的,加上k则为O(n^3),需要优化
考虑到第三维是累加长度为j-1的所有d[k],a[k]<a[i]
可以用二维树状数组c[x][j]存储以值x为结尾的长度为j的上升子序列数量
这样第三维就能降到log(n),总复杂度O(n^2*log(n))
code:
#include<bits/stdc++.h>
using namespace std;
const int maxm=1e3+5;
const int mod=1e9+7;
int d[maxm][maxm];
int c[maxm][maxm];
int xx[maxm];
int a[maxm];
int n,m;
int lowbit(int i){
return i&-i;
}
void add(int x,int len,int t){
while(x<maxm){
c[x][len]+=t;
c[x][len]%=mod;
x+=lowbit(x);
}
}
int ask(int x,int len){
int ans=0;
while(x){
ans+=c[x][len];
ans%=mod;
x-=lowbit(x);
}
return ans;
}
signed main(){
int T;
cin>>T;
int cas=1;
while(T--){
memset(c,0,sizeof c);
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>a[i];
xx[i]=a[i];
}
sort(xx+1,xx+1+n);
int num=unique(xx+1,xx+1+n)-xx-1;
for(int i=1;i<=n;i++){
a[i]=lower_bound(xx+1,xx+1+num,a[i])-xx+3;
}
for(int i=1;i<=n;i++){
d[i][1]=1;
add(a[i],1,1);
for(int j=2;j<=m&&j<=i;j++){
d[i][j]=ask(a[i]-1,j-1);
add(a[i],j,d[i][j]);
}
}
int ans=0;
for(int i=1;i<=n;i++){
ans+=d[i][m];
ans%=mod;
}
printf("Case #%d: ",cas++);
cout<<ans<<endl;
}
return 0;
}
CodeForces833 B. The Bakery
题意:
给长度为n的数组a,和一个整数k
要求把数组分成连续的k段,每段的权值是该段中不同数的个数,
输出最大权值和。
数据范围:n<=35000,k<=min(n,50),1<=a(i)<=n
完整题意:
n个蛋糕k个盒子,要求把蛋糕分成来k段,每段连续,分别用盒子装,
每个盒子的价值是盒子内不同蛋糕的数量,要求计算最大价值
解法:
d[i][j]表示前i个盒子装下前j蛋糕的最大价值
col[i][j]表示区间[i,j]中不同数的个数
显然d[1][j]=col[1][j]
考虑在原来的基础上再切一刀,可推出转移方程为:
d[i][j]=max{d[i-1][k]+col[k+1][i]},其中k<j
因为要枚举i,j,k,因此复杂度是O(n^2*k)的
而n最大35000,这个复杂度显然是不满足要求的,想办法优化掉一个n
因为d[i][j]=max{d[i-1][k]+col[k+1][i]},max操作可以想到线段树
但是要先将d[i-1][k]+col[k+1][i]全部计算出来
建立一颗线段树,第k个位置为d[i-1][k-1]
考虑每个数有价值的区间:
每个数有价值的范围由前一个与他相同数的位置决定,例如:
a[3]=5,a[5]=5,则a[5]有价值的区间为[4,5],
通过记录前一个数的位置可以O(n)把每个数有价值的区间求出来
假如一个数j的有价值区间为[a,b],则对线段树的[a,b]区间加1贡献
这样之后线段树中的每个位置就是d[i-1][k]+col[k+1][i]
取[1,j]中的max来更新d[i][j]
---
总结:
dp转移的过程中,一些数据需要直接计算
有时候可以拆成若干小数据分别计算贡献
code:
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int maxm=4e4+5;
int laz[maxm<<2],t[maxm<<2];
int d[55][maxm];
int mark[maxm];
int pre[maxm];
int a[maxm];
int n,k;
void pushup(int node){
t[node]=max(t[node*2],t[node*2+1]);
}
void pushdown(int node){
if(laz[node]){
laz[node*2]+=laz[node];
laz[node*2+1]+=laz[node];
t[node*2]+=laz[node];
t[node*2+1]+=laz[node];
laz[node]=0;
}
}
void build(int l,int r,int node,int i){
t[node]=laz[node]=0;
if(l==r){
t[node]=d[i-1][l-1];
return ;
}
int mid=(l+r)/2;
build(l,mid,node*2,i);
build(mid+1,r,node*2+1,i);
pushup(node);
}
void update(int st,int ed,int l,int r,int node){
if(st<=l&&ed>=r){
laz[node]++;
t[node]++;
return ;
}
pushdown(node);
int mid=(l+r)/2;
if(st<=mid)update(st,ed,l,mid,node*2);
if(ed>mid)update(st,ed,mid+1,r,node*2+1);
pushup(node);
}
int ask(int st,int ed,int l,int r,int node){
if(st<=l&&ed>=r){
return t[node];
}
pushdown(node);
int mid=(l+r)/2;
int ans=0;
if(st<=mid)ans=max(ans,ask(st,ed,l,mid,node*2));
if(ed>mid)ans=max(ans,ask(st,ed,mid+1,r,node*2+1));
return ans;
}
signed main(){
cin>>n>>k;
for(int i=1;i<=n;i++){
cin>>a[i];
}
for(int i=1;i<=n;i++){
pre[i]=mark[a[i]]+1;
mark[a[i]]=i;
}
for(int i=1;i<=k;i++){
build(1,n,1,i);
for(int j=1;j<=n;j++){
update(pre[j],j,1,n,1);
d[i][j]=ask(1,j,1,n,1);
}
}
cout<<d[k][n]<<endl;
return 0;
}
UVA1401 Remember the Word
题意:
给定串S,长度不超过3e5
再给n个串t(i),每个串长度不超过100,n<=4e4
现在要将S拆分为若干t(i)的连接,问有多少种组合方法,答案对20071027取模
样例:
S串:abcd
t(i):a、b、cd、ab
有两种组合方法:a+b+cd,ab+cd
解法:
d[i]表示从位置i开始的串的后缀分解方案数那么答案为d[0]
那么显然有转移方程:d[i]=sum{d[i+len(x)]},其中x为给定的串,且x是后缀suf(i)的前缀
最多4000个串,如果暴力用后缀suf(i)去判断是否有前缀串,肯定不行
可以先对给定串建立字典树,在字典树上找匹配串,因为串的长度不超过100,每次最多搜100层
总结:多串匹配转移可以用字典树减少总比较次数
code:
#include<bits/stdc++.h>
using namespace std;
const int maxm=4e5+5;
const int mod=20071027;
vector<int>temp;
struct Trie{
int a[maxm*26][26],tot;
int dep[maxm*26];//字符串长度
void init(){
for(int i=0;i<=tot;i++){
for(int j=0;j<26;j++)a[i][j]=0;
dep[i]=0;
}
tot=0;
}
void add(char* s){
int node=0;
int len=strlen(s);
for(int i=0;i<len;i++){
int v=s[i]-'a';
if(!a[node][v])a[node][v]=++tot;
node=a[node][v];
}
dep[node]=len;
}
void get(char *s,int len){
int node=0;
for(int i=0;i<len;i++){
int v=s[i]-'a';
if(!a[node][v])return ;
node=a[node][v];
if(dep[node])temp.push_back(dep[node]);
}
}
}T;
char s[maxm];
int d[maxm];
signed main(){
int cas=1;
while(scanf("%s",s)!=EOF){
T.init();
int n;scanf("%d",&n);
for(int i=1;i<=n;i++){
char t[105];scanf("%s",t);
T.add(t);
}
int len=strlen(s);
for(int i=0;i<=len;i++)d[i]=0;
d[len]=1;
for(int i=len-1;i>=0;i--){
temp.clear();
T.get(s+i,len-i);
for(int v:temp){
d[i]=(d[i]+d[i+v])%mod;
}
}
printf("Case %d: %d\n",cas++,d[0]);
}
return 0;
}
hdu4719 Oh My Holy FFF
题意:
给定长度为n的序列a,和一个整数L
你需要把这个序列分成若干连续段,要求每一段的长度不超过L
假设你分了M段,设第i段的最右边一个值为b(i),还需要满足b(i)>b(i-1)
当前分段方式的权值为:
问最大权值是多少,如果无法按照题目要求分段,输出No solution
数据范围:n<=1e5
解法:
d[i]表示i作为结尾的的最大价值
d[i]=max{d[k]-a[k]}+d[j]*d[j],其中k取值为[j-L,j-1],a[j]>a[k]
max{d[k]-a[k]}部分可以使用线段树,
但是由于存在a[j]>a[k]的限制,直接使用线段树可能找到不满足条件的k
一种巧妙的方法是按照a的大小,从小到大进行dp,计算出d[]之后加入线段树
这样每次找到的肯定都是满足条件的k了
需要注意的点:
数组中可能存在相同的数,显然根据题目条件相同的数是不能转移的
因此排序的过程中,遇到相同的数,需要把下标大的放在前面,
让下标大的先dp
总结:
见识到了dp顺序不一定是固定的从左到右
遇到这题这种限制条件时,
可以去限制拓扑序,令不能转移过来的点后面再计算,
从而使转移满足题目条件
code:
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int maxm=1e5+5;
struct ST{
int a[maxm<<2];
void build(int l,int r,int node){
a[node]=-1;
if(l==r)return ;
int mid=(l+r)/2;
build(l,mid,node*2);
build(mid+1,r,node*2+1);
}
void update(int x,int val,int l,int r,int node){
if(l==r){
a[node]=val;
return ;
}
int mid=(l+r)/2;
if(x<=mid)update(x,val,l,mid,node*2);
else update(x,val,mid+1,r,node*2+1);
a[node]=max(a[node*2],a[node*2+1]);
}
int ask(int st,int ed,int l,int r,int node){
if(st<=l&&ed>=r)return a[node];
int mid=(l+r)/2;
int ans=-1;
if(st<=mid)ans=max(ans,ask(st,ed,l,mid,node*2));
if(ed>mid)ans=max(ans,ask(st,ed,mid+1,r,node*2+1));
return ans;
}
}t;
int a[maxm];
int b[maxm];
int d[maxm];
int n,L;
bool cmp(int i,int j){
if(a[i]==a[j])return i>j;
return a[i]<a[j];
}
signed main(){
ios::sync_with_stdio(0);
int T;cin>>T;
int cas=1;
while(T--){
cin>>n>>L;
for(int i=1;i<=n;i++)cin>>a[i];
for(int i=1;i<=n;i++)b[i]=i;
sort(b+1,b+1+n,cmp);
t.build(0,n,1);
for(int i=1;i<=n;i++){
int x=b[i];
d[x]=-1;
if(x<=L)d[x]=a[x]*a[x];
//
int temp=t.ask(max(0LL,x-L),x-1,0,n,1);
if(temp>=0)d[x]=temp+a[x]*a[x];
//
if(d[x]>=0)t.update(x,d[x]-a[x],0,n,1);
}
cout<<"Case #"<<cas++<<": ";
if(d[n]<0)cout<<"No solution"<<endl;
else cout<<d[n]<<endl;
}
return 0;
}