模板整理——dp,字符串及其他
- dp
鬼知道我为啥把这么重要的东西放最后面啊。
考虑设计无后效性的状态,减小运算量。
- 背包
- 01 背包(\(\mathcal O(nm)\))
#include"iostream"
#include"cstdio"
#include"cmath"
using namespace std;
#define read(x) scanf("%d",&x)
#define MAXN 1005
int n,w[MAXN],v[MAXN],m;
int dp[MAXN];
int ans=0;
int main()
{
read(m),read(n);
for(int i=1;i<=n;i++) read(w[i]),read(v[i]);
for(int i=1;i<=n;i++)
{
for(int j=m;j>=w[i];j--) dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
}
for(int i=1;i<=m;i++) ans=max(dp[i],ans);
return printf("%d\n",ans),0;
}
- 完全背包(\(\mathcal O(nm)\))
#include"iostream"
#include"cstdio"
#include"cmath"
using namespace std;
#define read(x) scanf("%d",&x)
#define ll long long
int n,w[10005],v[10005],m;
ll dp[10000005];
ll ans=0;
int main()
{
read(m),read(n);
for(int i=1;i<=n;i++) read(w[i]),read(v[i]);
for(int i=1;i<=n;i++)
{
for(int j=w[i];j<=m;j++) dp[j]=max(dp[j],dp[j-w[i]]+(ll)v[i]);
}
for(int i=1;i<=m;i++) ans=max(dp[i],ans);
return printf("%lld\n",ans),0;
}
-
二进制拆分优化(\(\mathcal O(nm\log c)\))
#include"iostream"
#include"cstdio"
#include"cmath"
using namespace std;
int dp[1005];
int n,m;
int w[10005],v[10005],c[10005];
int h1,h2,m1,m2;
int ans=0;
int main()
{
scanf("%d:%d%d:%d%d",&h1,&m1,&h2,&m2,&n);
if(m2>=m1) m=(m2-m1)+60*(h2-h1);
else m=(m2-m1+60)+60*(h2-h1-1);
for(int i=1;i<=n;i++) scanf("%d%d%d",&w[i],&v[i],&c[i]);
for(int i=1;i<=n;i++)
{
if(!c[i]) c[i]=1<<20;
for(int k=1;c[i]>0;k<<=1)
{
int kk=min(k,c[i]);
for(int j=m;j>=kk*w[i];j--) dp[j]=max(dp[j],dp[j-kk*w[i]]+kk*v[i]);
c[i]-=kk;//一定要随拆随算
}
}
for(int i=1;i<=m;i++) ans=max(ans,dp[i]);
printf("%d\n",ans);
}
- 单调队列优化(\(\mathcal O(nm)\))
字母错一个,调题一万年。
#include"iostream"
#include"cstring"
#include"cstdio"
#include"cmath"
using namespace std;
int dp[1005];
int n,m;
int w[10005],v[10005],c[10005];
int h1,h2,m1,m2;
int ans=0;
int q[1005],loc[1005];
int d[1005];
int tail,head=0;
int main()
{
scanf("%d:%d%d:%d%d",&h1,&m1,&h2,&m2,&n);
if(m2>=m1) m=(m2-m1)+60*(h2-h1);
else m=(m2-m1+60)+60*(h2-h1-1);
for(int i=1;i<=n;i++) scanf("%d%d%d",&w[i],&v[i],&c[i]);
for(int i=1;i<=n;i++)
{
if(!c[i])
for(int j=w[i];j<=m;j++) dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
else
{
for(int j=0;j<=m;j++) d[j]=dp[j];//这个地方可能重复更新,而且不容易像01背包一样去倒叙枚举,所以这样改比较好
for(int k=0;k<w[i];k++)
{
head=0,tail=1,q[1]=0,loc[1]=0;
for(int j=0;j*w[i]+k<=m;j++)
{
d[k+j*w[i]]=max(d[k+j*w[i]],q[tail]+j*v[i]);
while(head>=tail&&dp[k+j*w[i]]-j*v[i]>=q[head]) head--;
q[++head]=dp[k+j*w[i]]-j*v[i],loc[head]=j;
while(loc[tail]<=j-c[i]) tail++;
}
}
for(int j=0;j<=m;j++) dp[j]=d[j];
}
}
for(int i=1;i<=m;i++) ans=max(ans,dp[i]);
printf("%d\n",ans);
return 0;
}
- 分组背包(\(\mathcal O((n+c)m)\))(\(c\) 为组数)
#include"algorithm"
#include"iostream"
#include"cstring"
#include"cstdio"
#include"cmath"
using namespace std;
#define MAXN 1005
#define read(x) scanf("%d",&x)
int n,m;
struct node
{
int w,v,c;
}a[MAXN];
int dp[MAXN],cnt[MAXN],ans=0,cp=0;
bool cmp(node n,node m){return n.c<m.c;}
int main()
{
read(m),read(n);
for(int i=1;i<=n;i++) read(a[i].w),read(a[i].v),read(a[i].c);
sort(a+1,a+n+1,cmp);
int lst=0;
for(int i=1;i<=n;i++)
{
if(a[i].c!=lst) cp++,lst=a[i].c;
cnt[cp]++;
}
int now=0;
for(int i=1;i<=cp;i++)
{
for(int j=m;j>=0;j--)
{
lst=now;
for(int k=1;k<=cnt[i];k++)
{
lst++;
if(j<a[lst].w) continue;
dp[j]=max(dp[j],dp[j-a[lst].w]+a[lst].v);
}
if(!j) now=lst;
}
}
for(int i=1;i<=m;i++) ans=max(ans,dp[i]);
printf("%d\n",ans);
return 0;
}
- 最长上升子序列(LIS)(\(\mathcal O(n\log n)\))
这是个套着 \(LCS\) 皮的 \(LIS\)。
- 朴素做法(二分)
#include"iostream"
#include"cstdio"
#include"cmath"
using namespace std;
#define MAXN 100005
#define read(x) scanf("%d",&x)
int n,x;
int a[MAXN],b[MAXN];
int now[MAXN],len=1;
int main()
{
read(n);
for(int i=1;i<=n;i++) read(x),b[x]=i;
for(int i=1;i<=n;i++) read(x),a[i]=b[x];
now[1]=a[1];
for(int i=2;i<=n;i++)
{
int l=1,r=len,mid;
if(a[i]>now[len]){now[++len]=a[i];continue;}
while(l<r)
{
mid=(l+r)>>1;
if(now[mid]<a[i]) l=mid+1;
else r=mid;
}
if(l<len&&now[l]<a[i]) l--;
now[l]=a[i];
}
printf("%d\n",len);
return 0;
}
- 大常数解法(线段树)
请参考 P2215
#include"algorithm"
#include"iostream"
#include"cstdio"
#include"cmath"
using namespace std;
#define read(x) scanf("%d",&x)
#define MAXN 10005
struct num
{
int val,id;
}b[MAXN];
int n,m;
struct node
{
int l,r,rec;
}a[MAXN<<2];
int re[MAXN],dp[MAXN];
int l,me[MAXN];
bool cmp(num n,num m){if(n.val==m.val) return n.id>m.id;else return n.val<m.val;}
void hash()
{
sort(b+1,b+n+1,cmp);
for(int i=1;i<=n;i++) re[b[i].id]=i,dp[i]=-1;
}
void update(int k){a[k].rec=max(a[k<<1].rec,a[k<<1|1].rec);}
void build(int k,int l,int r)
{
a[k].l=l,a[k].r=r;
if(l==r){a[k].rec=dp[l];return;}
int mid=(l+r)>>1;
build(k<<1,l,mid),build(k<<1|1,mid+1,r);
update(k);
return;
}
void modify(int k,int x,int y)
{
if(a[k].l==a[k].r){a[k].rec=y;return;}
if(a[k<<1].r>=x) modify(k<<1,x,y);
else modify(k<<1|1,x,y);
update(k);
}
int query(int k,int l,int r)
{
if(a[k].l==l&&a[k].r==r) return a[k].rec;
int mid=(a[k].l+a[k].r)>>1;
if(r<=mid) return query(k<<1,l,r);
else if(l>mid) return query(k<<1|1,l,r);
else return max(query(k<<1,l,mid),query(k<<1|1,mid+1,r));
}
int main()
{
read(n);
for(int i=1;i<=n;i++) read(b[i].val),me[i]=b[i].val,b[i].id=i;
hash(),build(1,1,n);
for(int i=n;i>=1;i--)
{
int now=query(1,re[i],n);
if(now==-1) dp[i]=1;
else dp[i]=now+1;
modify(1,re[i],dp[i]);
}
read(m);
while(m--)
{
read(l);
int now=l,lst=-0x7fffffff;
for(int i=1;i<=n;i++)
{
if(dp[i]>=now&&me[i]>lst)
{
now--,lst=me[i];
printf("%d ",me[i]);
if(now==0) break;
}
}
if(now>0) printf("Impossible");
puts("");
}
return 0;
}
- 字符串
这个东西我真的不大会啊/kk。
- 字符串哈希(这里直接使用双模哈希)(\(\mathcal O(nm+n\log n)\))
#include"algorithm"
#include"iostream"
#include"cstring"
#include"cstdio"
#include"cmath"
using namespace std;
#define MOD 1777777777
#define MoD 1000000007
int n;
struct str
{
int len,op,rt;
str(){op=rt=0;}
}s[10005];
char c[1505];
int mio[1505],mir[1505];
int ans=0;
int tran(char s)
{
if(s>='a'&&s<='z') return s-'a';
else if(s>='A'&&s<='Z') return s-'A'+26;
else return s-'0'+52;
}
bool cmp(str s,str t)
{
if(s.len==t.len)
{
if(s.op==t.op) return s.rt<t.rt;
return s.op<t.op;
}
return s.len<t.len;
}
int main()
{
scanf("%d",&n);
mio[0]=62,mir[0]=62;
for(int i=1;i<=1501;i++)
{
mio[i]=1ll*mio[i-1]*62ll%MOD;
mir[i]=1ll*mir[i-1]*62ll%MoD;
}
for(int i=1;i<=n;i++)
{
scanf("%s",c);
s[i].len=strlen(c);
for(int j=0;j<s[i].len;j++)
{
s[i].op=(s[i].op+1ll*mio[j]*tran(c[j])%MOD)%MOD;
s[i].rt=(s[i].rt+1ll*mir[j]*tran(c[j])%MoD)%MoD;
}
}
sort(s+1,s+n+1,cmp);
ans=1;
for(int i=2;i<=n;i++)
{
if(s[i].len==s[i-1].len&&s[i].op==s[i-1].op&&s[i].rt==s[i-1].rt) continue;
else ans++;
}
printf("%d\n",ans);
return 0;
}
- 字典树(Trie)(\(\mathcal O(\sum len)\))
#include"iostream"
#include"cstdio"
#include"cmath"
#include"cstring"
using namespace std;
#define MAXN 500005
struct node
{
int son[27],t;
}a[MAXN];
int n,m;
char c[55];
int cnt=0;
void add(string s)
{
int p=0,len=s.size();
for(int i=0;i<len;i++)
{
if(!a[p].son[s[i]-'a']) a[p].son[s[i]-'a']=++cnt;
p=a[p].son[s[i]-'a'];
}
a[p].t++;
}
int find(string s)
{
int p=0,len=s.size();
for(int i=0;i<len;i++)
{
if(!a[p].son[s[i]-'a']) return -1;
p=a[p].son[s[i]-'a'];
}
if(a[p].t){a[p].t--;return 1;}
else return 2;
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%s",c);
add(c);
}
scanf("%d",&m);
for(int i=1;i<=m;i++)
{
scanf("%s",c);
int opt=find(c);
if(opt==-1) puts("WRONG");
else if(opt==1) puts("OK");
else puts("REPEAT");
}
return 0;
}
- KMP字符串匹配(\(\mathcal O(n+m)\))
#include"cstdio"
#include"cstring"
#include"iostream"
using namespace std;
#define MAXN 1000005
char s[MAXN],t[MAXN];
int f[MAXN],nxt[MAXN];
int tl,sl;
void get()
{
nxt[1]=0;
for(int i=2;i<=tl;i++)
{
int k=nxt[i-1];
while(k>0&&t[k+1]!=t[i]) k=nxt[k];
nxt[i]=(t[k+1]==t[i])?k+1:0;
}
return;
}
void KMP()
{
f[1]=(s[1]==t[1])?1:0;
for(int i=2;i<=sl;i++)
{
int k=f[i-1];
while(t[k+1]!=s[i]&&k>0) k=nxt[k];
f[i]=(t[k+1]==s[i])?k+1:0;
if(f[i]==tl) printf("%d\n",i-tl+1);
}
return;
}
int main()
{
scanf("%s%s",s,t);
tl=strlen(t),sl=strlen(s);
for(int i=tl;i>=1;i--) t[i]=t[i-1];
for(int i=sl;i>=1;i--) s[i]=s[i-1];
get(),KMP();
for(int i=1;i<=tl;i++) printf("%d ",nxt[i]);
return puts(""),0;
}
- manacher算法(\(\mathcal O(n)\))
应该考不到吧,不复习了qwq。
- AC自动机(简单版)
- AC自动机(加强版)
- AC自动机(二次加强版)(都是 \(\mathcal O(size\sum len)\))
放到这里,表示我曾经会过。
- 其他
- 二分查找(\(\mathcal O(m\log n)\))
请自动忽略缩进问题/kk。
//最后一个比他小的数
read(n),read(m);
for(int i=1;i<=n;i++) read(a[i]);
sort(a+1,a+n+1);
for(int i=1;i<=m;i++)
{
read(x);
int l=1,r=n,mid;
while(l<r)
{
mid=(l+r)>>1;
if(a[mid]>=x) r=mid;
else l=mid+1;
}
if(a[l]>=x) l--;
if(a[1]>=x) puts("NO!");
else printf("%d\n",a[l]);
cout<<endl;
}
return 0;
//最后一个比他小或等于的数
read(n),read(m);
for(int i=1;i<=n;i++) read(a[i]);
sort(a+1,a+n+1);
for(int i=1;i<=m;i++)
{
read(x);
int l=1,r=n,mid;
while(l<r)
{
mid=(l+r)>>1;
if(a[mid]>x) r=mid;
else l=mid+1;
}
if(a[l]>x) l--;
if(a[1]>x) puts("NO!");
else printf("%d\n",a[l]);
}
return 0;
//第一个比他大的数
read(n),read(m);
for(int i=1;i<=n;i++) read(a[i]);
sort(a+1,a+n+1,cmp);
for(int i=1;i<=m;i++)
{
read(x);
int l=1,r=n,mid;
while(l<r)
{
mid=(l+r)>>1;
if(a[mid]<=x) r=mid;
else l=mid+1;
}
if(a[l]<=x) l--;
if(x>=a[1]) puts("NO!");
else printf("%d\n",a[l]);
}
return 0;
//第一个比他大或等于的数
read(n),read(m);
for(int i=1;i<=n;i++) read(a[i]);
sort(a+1,a+n+1,cmp);
for(int i=1;i<=m;i++)
{
read(x);
int l=1,r=n,mid;
while(l<r)
{
mid=(l+r)>>1;
if(a[mid]<x) r=mid;
else l=mid+1;
}
if(a[l]<x) l--;
if(a[1]<x) puts("NO!");
else printf("%d\n",a[l]);
}
return 0;
- 归并排序(\(\mathcal O(n\log n)\))
#include"iostream"
#include"cstdio"
#include"cmath"
using namespace std;
#define MAXN 100005
int n;
int a[MAXN],rt[MAXN];
void qsort(int l,int r)
{
if(l==r) return;
int mid=(l+r)>>1;
qsort(l,mid),qsort(mid+1,r);
int c=l-1,i=l,j=mid+1;
while(i<=mid&&j<=r)
{
if(a[i]<=a[j]) rt[++c]=a[i++];
else rt[++c]=a[j++];
}
while(i<=mid) rt[++c]=a[i++];
while(j<=r) rt[++c]=a[j++];
for(i=l;i<=r;i++) a[i]=rt[i];
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
qsort(1,n);
for(int i=1;i<=n;i++) printf("%d ",a[i]);
return puts(""),0;
}
- 利用归并排序求逆序对(\(\mathcal O(n\log n)\))
#include"iostream"
#include"cstdio"
#include"cmath"
using namespace std;
#define MAXN 500005
int n;
int a[MAXN],rt[MAXN];
long long ans=0;
void qsort(int l,int r)
{
if(l==r) return;
int mid=(l+r)>>1;
qsort(l,mid),qsort(mid+1,r);
int c=l-1,i=l,j=mid+1;
while(i<=mid&&j<=r)
{
if(a[i]<=a[j]) rt[++c]=a[i++];
else rt[++c]=a[j++],ans+=(long long)(mid-i+1);
}
while(i<=mid) rt[++c]=a[i++];
while(j<=r) rt[++c]=a[j++];
for(i=l;i<=r;i++) a[i]=rt[i];
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
qsort(1,n);
printf("%lld\n",ans);
return 0;
}
注:逆序对还可以利用树状数组来求,是一种二位偏序的思想,这里就不赘述了。
- 二维差分:
对于矩阵 \(\{a_{i,j}\}\),其查分数组计算方法为 \(\{p_{i,j}=a_{i,j}-a_{i-1,j}-a_{i,j-1}+a_{i-1,j-1}\}\)。
同时还原出原数 \(\{a_{i,j}=\sum\limits_{k=1}^i\sum\limits_{l=1}^jp_{k,l}\}\)。
可以类比一维差分与前缀和进行记忆。
快速修改。
比如说你要对 \((x_1,y_1):(x_2,y_2)\) 区间 \(+1\)。
画图模拟一下就知道应该对差分数组实施:
\[p_{x_1,y_1}+1,p_{x_1,y_2+1}-1,p_{x_2+1,y_1}-1,p_{x_2+1,y_2+1}+1
\]
- 一个常用的结论
如这个题(Link),我们考虑把操作的分成两组考虑。
- 获得大于 \(0\)
显然按消耗值从小到大排序。
- 获得小于 \(0\)(消耗)
显然这种不能简单的贪心,记住下面这个结论:
按 消耗与需求的和从大到小排序。
总起来,当然先做获得大于 \(0\) 的操作。
- 模拟退火(\(\mathcal O(\text{可能能过})\))
#include"iostream"
#include"cstdio"
#include"cmath"
#include"algorithm"
#include"ctime"
using namespace std;
double xo[1005],yo[1005],m[1005];
int n;
double ansx,ansy,x,y,X,Y;
double minx=10000000000.00;
double f(double xx,double yy)
{
double ans=0;
for(int i=1;i<=n;i++) ans+=sqrt((xx-xo[i])*(xx-xo[i])+(yy-yo[i])*(yy-yo[i]))*m[i];
return ans;
}
void SA()
{
double T=13000,delta=0.993,T0=1e-15;
while(T>T0)
{
X=x+((rand()<<1)-RAND_MAX)*T;
Y=y+((rand()<<1)-RAND_MAX)*T;
double op=f(X,Y);
if(op<minx) minx=op,ansx=x=X,ansy=y=Y;
else if(exp((minx-op)/T)*RAND_MAX>rand()) x=X,y=Y;
T*=delta;
}
}
void work(){while((double)clock()/CLOCKS_PER_SEC<0.9) SA();}
int main()
{
srand(19260817),srand(time(0)),srand(rand()),srand(rand());
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%lf%lf%lf",&xo[i],&yo[i],&m[i]),x+=xo[i],y+=yo[i];
x/=n,y/=n;
work();
return printf("%.3lf %.3lf\n",ansx,ansy),0;
}