MySQL異常 #SQLException: sql injection violation, part alway true condition not allow
動態規劃
目錄若起始時規定開始狀態的\(j\),可使\(i\)從\(n\)到0,輸出\(dp[0][j]\)。
可連結區間:可開\(dp[maxn][2]\)表示是否連線,也可一維\((dp[i-1]+a[i],\ dp[i-2]+b[i])\)。
\(dp[i]\)定義:要求\(i\)為嚴格連續則定義\(dp[i]\)為\(0-i\)且以\(i\)結尾的最優解,若要求\(i\)
揹包
最好從1開始計算物品,因為0可當作什麼也沒選。
0-1揹包
已知條件有第\(i\)個物品的重量\(w_i\),價值\(v_i\),以及揹包的總容量\(m\)。
\(f_{i,j}\) 為在只能放前\(i\)個物品的情況下,容量為\(j\)的揹包所能達到的最大總價值。
\(f_{i,j}=\max(f_{i-1,j},f_{i-1,j-w_i}+v_i)\)
\(fj=\max(f_j,f_{j-w_i}+v_i)\) 從右到左。
完全揹包
與 0-1 揹包的區別僅在於一個物品可以選取無限次,而非僅能選取一次。
\(f_{i,j}=\max_{k=0}^{+\infty}(f_{i-1,j-k\times w_i}+k\times v_i)\)
\(f_{i,j}=\max(f_{i-1,j},f_{i,j-w_i}+v_i)\)
\(fj=\max(f_j,f_{j-w_i}+v_i)\) 從左到右
類似揹包
poj-1015:從\(n\)個物品(有屬性\(x,y\))中選出\(m\)個,要求在\(\min(\mid\sum x-\sum y\mid)\)的前提下
\(\max(\sum x +\sum y)\),三重迴圈,\(j\)當前正在選擇,\(k\)當前差,\(i\)當前可在選,\(dp[j][k]\)當前最大和。
注:輸出時不再按照從小到大選擇\(i\),不能僅使用函式遞迴,應重排序。
參考題解。
錯誤程式碼:
path處理錯誤,eg:(1)和(2)同時是\(\max(dp[i][j])\)
for(int j=0; j<m; j++){
for(int k=0; k<=m*40; k++){
if(dp[j][k]>=0){
for(int i=1; i<=n; i++){
if(dp[j][k]+x[i]+y[i]>dp[j+1][k+x[i]-y[i]]){
a=j, b=k;
while(path[a][b]!=i && a>0){
b-=x[path[a][b]]-y[path[a][b]];
a--;
}
if(a==0){//未選
dp[j+1][k+x[i]-y[i]]=dp[j][k]+x[i]+y[i];
path[j+1][k+x[i]-y[i]]=i;
}
}
}
}
}
}
狀壓DP
例:工作排序,狀態從\(n!\)到\(1<<n-1\),外層迴圈\(1<<n-1\),內層迴圈\(n\)。
沒有要求整項工作在規定時間做完,所以不能貪心。
#define maxn 20
#define maxm 1<<20
#define inf 1e9
struct node{
char s[105];
int d, c;
}a[maxn];
int dp[maxm], t[maxm];
int pre[maxm];
void op(int x){ //注意輸出
if(x==0) return;
op(x^(1<<pre[x]));
printf("%s\n", a[pre[x]].s);
}
int main(){
int T, n, m;
cin>>T;
while(T--){
scanf("%d", &n);
for(int i=0; i<n; i++)
scanf("%s %d %d", a[i].s, &a[i].d, &a[i].c);
//name, deadline, continue_time
m=1<<n;
dp[0]=0, t[0]=0;
for(int i=1; i<m; i++){
dp[i]=(int)inf;
for(int j=0; j<n; j++){
int d=a[j].d, c=a[j].c, bit=1<<j;
if((i&bit)==0) continue;
int dt=t[i^bit]+c-d;
if(dt<0) dt=0;
if(dp[i]>=dp[i^bit]+dt){
dp[i]=dp[i^bit]+dt;
t[i]=t[i^bit]+c;
pre[i]=j;
}
}
}
printf("%d\n", dp[m-1]);
op(m-1);
}
return 0;
}
區分:每項工作有continue和固定start,則簡單\(dp\)。\(O(n^2)\)
區間DP
卡特蘭分法
多邊形切割
將多邊形可按卡特蘭數分法分割,以\(i,j\)為頂點分割線有代價\(f(i,j)\),求最小分法。
初始化\(dp[i][i+1]=cost[i][i+1]=0\)記憶化搜尋即可。\(O(n^3)\)
ll co(int i, int j){
if(c[i][j]!=-1) return c[i][j];
node a=h[i], b=h[j];
return c[i][j]=Abs(a.x+b.x)*Abs(a.y+b.y)%m;
}
ll sol(int i, int j){
if(dp[i][j]!=-1) return dp[i][j];
ll res=inf;
for(int k=i+1; k<j; k++){
res=min(res, sol(i, k)+co(i, k)+sol(k, j)+co(k, j));
}
return dp[i][j]=res;
}
棧的進出
給定每個時刻棧頂元素,求最小輸入資料個數。
初始化\(dp[i][i]=1\),\(dp[i+1][i]=0\)可不寫,\(dp[i][j]=dp[i][j-1]\)(選新的),\(dp[i][j]=dp[i][k]+dp[k+1][j-1],\ \{k\mid a[k]==a[j]\ \&\&\ i<=k<j\}\)(用以前的)。\(O(n^3)\)
int sol(int i, int j){
if(dp[i][j]!=-1) return dp[i][j];
int res=sol(i, j-1)+1;
if(a[j]==a[j-1]) res=min(res, dp[i][j-1]);
for(int k=i; k<=j-2; k++){
if(a[k]==a[j])
res=min(res, sol(i, k)+sol(k+1, j-1));
}
return dp[i][j]=res;
}
括號配對
s應為: ab 或 (a)。
不連續:即())()=4。
其中一種方法:
\(dp[i][j]=dp[i+1][j-1]+2,\ \{a[i]=='(' \&\& a[j]==')'\}\),\(dp[i][j]=dp[i][k]+dp[k+1][j],\ \{k\mid i<=k, k+1<=j\}\)
取最小值即可。
連續:即())()=2。(沒有題目提交過,不保證正確),用棧或者:
\(dp[i][j]=1,\ \{a[i]=='(' \&\& a[j]==')' \&\& dp[i+1][j-1]\}\),\(dp[i][j]=1,\ \{dp[i][k]\&\&dp[k+1][j]\&\&i<k,\ k+1<j\}\)
數列取數
數列兩端取數
\(dp[i][j]=F(f(dp[i+1][j],\ a[i]),\ f(dp[i][j-1],\ a[j]))\)
//eg:score=cnt(選取順序)*a[i],求maxn(score),不用初始化
for(int i=n; i>=1; i--){
for(int j=i; j<=n; j++){
dp[i][j]=max(dp[i+1][j]+a[i]*(n-(j-i)),
dp[i][j-1]+a[j]*(n-(j-i)));
}
}
數列中間取數
1,n最後取,\(dp[i][j]\)為去完\(i,j\)中間的數最小或最大代價。
\(dp[i][j]=dp[i][k]+dp[k][j]+f[i,k,j]\),最後取\(k\)。
int sol(int i, int j){
if(dp[i][j]!=-1) return dp[i][j];
if(i+1==j) return dp[i][j]=0;
int res=(int)inf;
for(int k=i+1; k<j; k++){
res=min(res, sol(i, k)+sol(k, j)+a[i]*a[k]*a[j]);
}
return dp[i][j]=res;
}
可入棧的取數
已有順序1到n,和代價a[i],個人代價為前面出場人數乘a[i],有一空棧可用於調整順序。
//錯誤:沒有考慮中間出場
int solwr(int i, int j){
if(dp[i][j]!=-1) return dp[i][j];
if(i>=j) return dp[i][j]=0;
return dp[i][j]=min(s[j]-s[i], a[i]*(j-(i+1)+1))+solwr(i+1, j);
}
int sol(int i, int j){
if(dp[i][j]!=-1) return dp[i][j];
if(i>=j) return dp[i][j]=0;
int res=s[j]-s[i]+sol(i+1, j);
for(int k=i+1; k<=j; k++){
res=min(res, sol(i+1, k)+a[i]*(k-(i+1)+1)+
(s[j]-s[k])*(k-i+1)+sol(k+1, j));
}
return dp[i][j]=res;
}
段覆蓋
覆蓋空字串
每次可將\(a\)的一連續段改為某一字元,求將空字串\(a\)修改為\(s\)的最小修改次數。
沒有重複字元:\(dp[j][i]=dp[j+1][i]+1=dp[j][k]+dp[k+1][i]\)
顯然,必須採取先覆蓋兩端邊界為最優解,故:
\(s[j]==s[i]:\ dp[j][i]=dp[j+1][i]=dp[j][i-1]\)
這裡固定\(i\)即可計算。
memset(dp, 0, sizeof(dp));
for(int i=0; i<n; i++){
for(int j=i; j>=0; j--){
dp[j][i]=dp[j+1][i]+1;
for(int k=j+1; k<=i; k++){
if(s[j]==s[k])
dp[j][i]=min(dp[j+1][k]+dp[k+1][i], dp[j][i]);
}
}
}
覆蓋非空字串
先計算空字串,\(S[i]=min(dp[0][i],\ S[i-1])+1\)。
\(a[k]==s[k]:\ S[i]=min(S[i],\ S[k-1]+dp[k+1][i])\)
S[0]=(a[0]==s[0]?0:1);
for(int i=1; i<n; i++){
S[i]=min(dp[0][i], S[i-1]+1);
for(int k=0; k<=i; k++){
if(a[k]==s[k]) S[i]=min(S[i], S[k-1]+dp[k+1][i]);
}
}
常見
最長上升子序列
\(dp[i]\)初始化為\(1\)。\(O(n^2)\)
例:疊加最高立方體。
例:HDU-1257,不是多次查詢最長不嚴格下降子序列,而是直接找最長上升子序列。若\(ans\)大於最長上升子序列,必有一個不在序列中點當起點,該點可來源於前一個起點非嚴格連續下降,故矛盾;若\(ans\)小於最長上升子序列,必有一個在序列中點不當起點,該點為字首最大值,必為起點,故矛盾。
錯誤程式碼:
ans=0;
for(int i=0; i<n; i++){
dp[i]=1;
for(int j=0; j<i; j++){
if(a[j]<a[i]){
dp[i]=max(dp[i], dp[j]+1);
ans=max(ans, dp[i]);//可能永遠不會執行這一句,而ans應為1
}
}
}
正確程式碼:
for(int i=0; i<n; i++){
dp[i]=1;
for(int j=0; j<i; j++){
if(a[j]<a[i]){
dp[i]=max(dp[i], dp[j]+1);
}
}
ans=max(ans, dp[i]);
}
//or 初始化ans=1
\(o(n\log(n))\)
int a[maxn], b[maxn];
int dp(int n){
int len, pos;
b[1]=a[1];
len=1;//b的size
for(int i=2; i<=n; i++){
if(a[i]>=b[len]) b[++len]=a[i];//非降序列
else{ //第一個比 a[i] 大的位置
pos=upper_bound(b+1, b+1+len, a[i])-b;
b[pos]=a[i];
}
}
return len;
}
最長公共子序列
\(O(n^2)\)
//x,y前各加了一個不相等的無關字元,使字串向後移一位
if(x[i]==y[j]) dp[i][j]=dp[i-1][j-1]+1;
else dp[i][j]=max(dp[i][j-1], dp[i-1][j]);
空間優化
c^=1;//使用亦或翻滾
for(int j=1; j<sy; j++){
if(x[i]==y[j]) dp[j][c]=dp[j-1][c^1]+1;
else dp[j][c]=max(dp[j][c^1], dp[j-1][c]);
}
最大對稱矩陣
\(dp[i][j]\)為以\((i,j)\)結尾的最大正方形。
(左上右下對稱)\(dp[i][j]=min(dp[i-1][j-1], \min(k)),\{k\mid a[i-k][j]!=a[i][j-k]\}\)
數列單調代價
可加減調整數列為非嚴格遞增數列,每次調整代價\(abs(pre-now)\)。
因為調整後必為原陣列中的數,離散化\(a[i]\)。\(dp[i][j]\)為選取第\(i\)個數為\(j\)的最小代價。
\(dp[i][j]=\Delta a+\min(dp[i][k]),\ \{k\mid 1<=k<=j\}\)
一維:\(dp[j]=\Delta a+pm[j]\),\(pm[j]\)為上一行\(\min(dp[k]),\ \{k\mid 1<=k<=j\}\)
sort(b+1, b+1+n);
pm[0]=(ll)inf;
for(int i=1; i<=n; i++){
for(int j=1; j<=n; j++){
dp[j]=pm[j]+Abs(a[i]-b[j]);
pm[j]=min(dp[j], pm[j-1]);
}
}
斜率DP
eg:HDU-3507 ,給出數列\(a[i]\),列印連續數列段\(k\)到\(j\)代價為\((sum[j]-sum[k-1])^2+m\),求列印全部數列最小代價。
比較\(k+1,\ j+1\)哪個更適合和\(i\)放一起,\(k+1<j+1<i\):
\(dp[j]+(s[i]-s[j])^2+m<=dp[k]+(s[i]-s[k])^2+m\)
\(dp[j]+s[j]^2-2\times s[i]\times s[j]<=dp[k]+s[k]^2-2\times s[i]\times s[k]\)
\((dp[j]+s[j]^2)-(dp[k]+s[k]^2)<=2\times(s[j]-s[k])\times s[i]\)
\(K=\frac{(dp[j]+s[j]^2)-(dp[k]+s[k]^2)}{2\times(s[j]-s[k])\times s[i]}<=s[i]\)
即:\(K<s[i]\)時,大的\(j+1\)更合適。
一至三點\(k<j<i\),判斷誰更適合點\(x\)。
下凸:\(K_1<K_2\):
1)\(K_1<K_2<s[x]\),\(i\)最優
2)\(K_1<s[x]<K_2\),\(j\)最優
3)\(s[x]<K_1<K_2\),\(k\)最優
故可採用維持佇列下凸,僅比較相鄰點可找到最優,即在\(O(n)\)內完成。
下凹:\(K_1>K_2\):
1)\(s[x]>K_1>K_2\),\(i\)最優
2)\(K_1>s[x]>K_2\),\(k,\ i\)都比\(j\)優,需再比較\(K_{k,\ i}\)與\(s[x]\)判斷最終誰優。
3)\(K_1>K_2>s[x]\),\(k\)最優。
故可採用維持佇列下凸,需比較任意兩點可找到最優,即在\(O(n^2)\)內完成。
本題採用\(O(n^2)\)超時,故維護下凸即可。
證明如下操作後,最終中間過程以及最終獲得佇列必為下凸:採用數學歸納。
ll dp[maxn], a[maxn], s[maxn], m;
int q[maxn], n;
ll up(int j, int k){
return (dp[j]+s[j]*s[j])-(dp[k]+s[k]*s[k]);
}
ll dow(int j, int k){
return 2*(s[j]-s[k]);
}
ll sol(int j, int i){//選j+1到i
return dp[j]+m+(s[i]-s[j])*(s[i]-s[j]);
}
int main(){
while(scanf("%d%lld", &n, &m)!=EOF){
for(int i=1; i<=n; i++){
scanf("%lld", &a[i]);
s[i]=s[i-1]+a[i];
}
int st=0, t=0;
q[t++]=0;
for(int i=1; i<=n; i++){
while(st+1<t && up(q[st+1], q[st])<=s[i]*dow(q[st+1], q[st]))
st++;
dp[i]=sol(q[st], i);
while(st+1<t && up(i, q[t-1])*dow(q[t-1], q[t-2])<=
up(q[t-1], q[t-2])*dow(i, q[t-1])) t--;
q[t++]=i;
}
printf("%lld\n", dp[n]);
}
}