【破環成鏈】【區間dp】LGP4342 [IOI1998]Polygon
【破環成鏈】【區間dp】LGP4342 [IOI1998]Polygon
題目可能有些許修改,但大意一致
多邊形是一個玩家在一個有n個頂點的多邊形上的遊戲,如圖所示,其中n=4。每個頂點用整數標記,每個邊用符號+(加)或符號*(乘積)標記。
第一步,刪除其中一條邊。隨後每一步:
選擇一條邊連線的兩個頂點V1和V2,用邊上的運算子計算V1和V2得到的結果來替換這兩個頂點。
遊戲結束時,只有一個頂點,沒有多餘的邊。
如圖所示,玩家先移除編號為3的邊。之後,玩家選擇計算編號為1的邊,然後計算編號為4的邊,最後,計算編號為2的邊。結果是0。
(翻譯者友情提示:這裡每條邊的運算子旁邊的數字為邊的編號,不拿來計算)
編寫一個程式,給定一個多邊形,計算最高可能的分數。
輸入格式
輸入描述一個有n個頂點的多邊形,它包含兩行。第一行是數字n,為總邊數。
第二行描述這個多邊形,一共有2n個讀入,每兩個讀入中第一個是字元,第二個是數字。
第一個字元為第一條邊的計算符號(t代表相加,x代表相乘),第二個代表頂點上的數字。首尾相連。
3 < = n < = 50
對於任何一系列的操作,頂點數字都在[-32768,32767]的範圍內。
輸出格式
第一行,輸出最高的分數。在第二行,它必須寫出所有可能的被清除後的邊仍能得到最高得分的列表,必須嚴格遞增。
感謝@2016c01 提供的翻譯
輸入輸出樣例
輸入 #1
4 t -7 t 4 x 2 x 5
很明顯這裡t表示這條邊是加法邊,然後後面那個數字表示這條邊指向的結點的權值。
然後x代表乘法邊。
輸出 #1
33
1 2
題意
首先定義邊的型別為加或者乘,然後每次可以選擇一條邊把兩邊的結點合併,合併運算就是結點內數字對應邊的型別的運算。
比如說權值為1的點和權值為2的點通過加法邊可以合併為\(1+2=3\)的一個點,繼承原來點點所有邊。
現在給出一個\(3\leq n\leq 50\)個點\(n\)條邊的簡單環,求進行\(n-1\)次操作後剩下那個點的點權最大值。
思路
進行\(n-1\)次操作,就相當於捨棄掉一條邊,而剩下的邊必須在區間上連續。
那麼我們首先也是要破環成鏈,複製一份放在後面
然後又是合併問題了。
定義\(dp_{i,j}\)
這裡要慢一點,我們設邊\(k\)的型別為\(\circ_k\),終點為第\(k\)個點
合併區間\([i,k)\)和區間\([k,j)\)用的就是\(\circ_k\)
然後合併的時候需要兩邊都有東西,所以\(k\in(i,j)\)
所以狀態轉移方程: \[dp_{i,j}=\max_{i<k<j}\{(dp_{i,k})\circ_k(dp_{k,j})\}\quad(0<i<j\leq n+1) \]
初始狀態:\(dp_{i,i+1}=val_i\)
結束狀態:因為是隻進行了\(n-1\)次操作,但是合併了\(n\)個點,所以是\(\max_{0\leq i<n}\{dp_{1+i,n+i+1}\}\)
轉移順序:
- 區間長度升序\(len:[2,n]\)
- 區間起點升序\(i:[1,n-len+1]\),同時定義區間終點\(j=i+len\)
- 中轉點升序\(k:(i,j)\)
注意到有負數所以\(dp\)陣列其他元素初始值要是負數的極小值
打完回來
\(80pts\)
然後發現漏考慮了一些情況。
這裡是有負數的。
是有負數乘法的。。。
負數乘以負數負負得正。
所以負數下乘數越小乘積越大。
所以我們不能只記錄一個最大值,還需要記錄一個最小值。
再分別討論一下。
對於加法,肯定是小的加小的得到最小的,大的加大的得到最大的。
至於乘法,最小的可能是兩個最小的相乘,也可能是最大的和最小的相乘,還可能是最大的和最小的相乘。。。
但是最大的就只有倆情況:最大和最大,最小和最小。
對於上述的五種情況,分別取個最值就可以維護最大值和最小值了
(真的有被坑到,不愧是IOI的題)
總結一下,整個題需要倆陣列\(dp_{i,j}\)表示合成\([i,j)\)的最大值,\(mn_{i,j}\)表示最小值,然後有
答案就是\(\max_{0\leq i<n}\{dp_{1+i,1+n+i}\}\)
Code
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int N=128;
inline const int x(const int a,const int b)
{
return a*b;
}
inline const int t(const int a,const int b)
{
return a+b;
}
const int(*p[N])(const int,const int);
int val[N],dp[N][N],mn[N][N];
char tpe[N];
int nn,n,ans;
int main()
{
cin>>nn;
memset(dp,0x80,sizeof(dp));
memset(mn,0x01,sizeof(mn));
for(register int i=1;i<=nn;++i) cin>>tpe[i]>>val[i];
for(register int i=1;i<=nn;++i) tpe[i+nn]=tpe[i];
for(register int i=1;i<=nn;++i) val[i+nn]=val[i];
n=nn<<1;
for(register int i=1;i<=n;++i) p[i]=tpe[i]=='t'? t:x;
for(register int i=1;i<=n;++i) mn[i][i+1]=dp[i][i+1]=val[i];
for(register int len=2;len<=n;++len)
{
for(register int i=1;i<=n-len+1;++i)
{
const int j=i+len;
for(register int k=i+1;k<j;++k)
{
dp[i][j]=max(dp[i][j],(*p[k])(dp[i][k],dp[k][j]));
dp[i][j]=max(dp[i][j],(*p[k])(mn[i][k],mn[k][j]));
mn[i][j]=min(mn[i][j],(*p[k])(mn[i][k],mn[k][j]));
mn[i][j]=min(mn[i][j],(*p[k])(mn[i][k],dp[k][j]));
mn[i][j]=min(mn[i][j],(*p[k])(dp[i][k],mn[k][j]));
}
}
}
ans=dp[0][0];
for(register int i=0;i<nn;++i)
{
ans=max(ans,dp[1+i][nn+i+1]);
}
cout<<ans<<endl;
for(register int i=0;i<nn;++i)
{
if(ans==dp[1+i][nn+i+1])
{
printf("%d ",i+1);
}
}
printf("\n");
return 0;
}