前言
\(Loj\) 放上了那么多《信息学奥赛一本通》上的题(虽然我并没有这本书),我要给它点一个大大的赞 ^_^
以后分类刷题不愁啦!
正文
那就一道道说吧。
石子合并
将 \(n\) 堆石子绕圆形操场排放,现要将石子有序地合并成一堆。规定每次只能选相邻的两堆合并成新的一堆,并将新的一堆的石子数记做该次合并的得分。
求 \(n−1\) 次合并后得分总和的最大值和最小值。
\(n \leq 200\)
首先注意到“绕圆形排放”,那么有一个经典的技巧就是在 \(1 \sim n\) 后再接一个 \(1 \sim n\) ,相当于把圆拆成一条直线
接着就是经典的区间 \(DP\) ,方程 \(f[i][j]=\mathop{\max}\limits_{k=i}^{j-1} \{f[i][k]+f[k+1][j]+sum[i][j] \}\),最小值亦然
\(sum[i][j]\)为第 \(i\) 堆到第 \(j\) 堆石子数之和,可以用前缀和算出来。
注意转移的顺序为区间大小从小至大。
#include
#include
#include
using namespace std;
const int N = 405;
int n;
int f[N][N],g[N][N],s[N];
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",&f[i][i]),f[i+n][i+n]=f[i][i];
for(int i=1;i<=n*2;i++)
s[i]=s[i-1]+f[i][i],f[i][i]=0;
for(int i=2;i<=n;i++)
for(int j=1;j+i-1<=n*2;j++){
g[j][i+j-1]=2*1e9;
for(int k=j;k
能量项链
在项链上有 \(N\) 颗能量珠。能量珠是一颗有头标记和尾标记的珠子,这些标记对应着某个正整数。并且,对于相邻的两颗珠子,前一颗珠子的尾标记必定等于后一颗珠子的头标记。如果一颗能量珠头标记为 \(m\),尾标记为 \(r\),后一颗能量珠头标记为 \(r\),尾标记为 \(n\),则聚合后释放出 \(m \times r \times n\) 单位的能量,新珠子头标记为 \(m\),尾标记为 \(n\) 。
项链上有 \(n\) 颗珠子,相邻两颗珠子可以合并成一个,合并同时会放出一定的能量,不同珠子合并放出能量不相同,请问按怎样的次序合并才能使得释放的能量最多?
\(n \leq 100\)
与第一题套路差不多,这个题所有能量珠也是围成一个圈的(吐槽一下,这个事情原题中说的很不清楚)
所以也是 \(1\sim n\) 后再接 \(1 \sim n\)
转移方程也很套路,\(f[i][j]=\mathop{\max}\limits_{k=i}^{j-1} \{ f[i][k]+f[k+1][j]+w[i] \times w[k] \times w[j] \}\)
#include
#include
#include
using namespace std;
const int N = 205;
typedef long long ll;
int n;
ll w[N],f[N][N];
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%lld",&w[i]),w[i+n]=w[i];
for(int i=3;i<=n+1;i++)
for(int j=1;j+i-1<=2*n;j++)
for(int k=j+1;k
凸多边形的划分
给定一个具有 \(N\) 个顶点的凸多边形,将顶点从 \(1\) 至 \(N\) 标号,每个顶点的权值都是一个正整数。将这个凸多边形划分成 \(N−2\) 个互不相交的三角形,试求这些三角形顶点的权值乘积和至少为多少。
\(N \leq 50\) ,每个点权值 \(\leq 10^9\)
这是一道好题!!(因为我居然想了好半天没想出来,最后看的题解【捂脸】)
之前没想到是因为不知道怎么让区间连续起来,有些三角形三个顶点都不连续……
后来才意识到,当前多边形每一条边都一定在一个三角形中,故只需要拿住一条边,再选一个点作为三角形另一个顶点,然后就会把多边形分为两个部分,而这两个部分的多边形顶点是连续的。好巧好巧!
多边形定点是连成一个圈的,故也要在 \(1 \sim n\) 后接 \(1 \sim n\)
方程 \(f[i][j]=\mathop{\max}\limits_{k=i+1}^{j-1} \{ f[i][k]+f[k][j]+w[i] \times w[k] \times w[j] \}\)
坑人的是要写高精度 \(qwq\)
#include
#include
#include
#include
using namespace std;
const int N = 55*2;
const int SZ =1000;
struct Bign{
int s[15],len;
Bign() { len=0; memset(s,0,sizeof(s)); }
void print(){
printf("%d",s[len-1]);
for(int i=len-2;i>=0;i--) {
if(s[i]<10) printf("0");
if(s[i]<100) printf("0");
printf("%d",s[i]);
}
printf("\n");
}
Bign operator = (int x) {
if(x==0) s[len++]=0;
while(x!=0) s[len++]=x%SZ,x/=SZ;
return *this;
}
Bign operator + (const Bign &b) const {
Bign c;
for(int i=0,g=0;;i++){
if(i>=len && i>=b.len && g==0) break;
c.s[c.len++]=(s[i]+b.s[i]+g)%SZ;
g=(s[i]+b.s[i]+g)/SZ;
}
return c;
}
Bign operator * (const Bign &b) const {
Bign c;
for(int i=0;i=c.len && c.s[i]==0 && g==0) { c.len=i; break; }
g=c.s[i]+g;
c.s[i]=g%SZ;
g/=SZ;
}
return c;
}
bool operator > (const Bign &b) const {
if(len!=b.len) return len>b.len;
for(int i=len-1;i>=0;i--)
if(s[i]!=b.s[i]) return s[i]>b.s[i];
return false;
}
}w[N],f[N][N],MAX;
int n;
int main()
{
int x;
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",&x),w[i]=x,w[i+n]=x;
MAX.len=15; MAX.s[14]=1;
for(int i=1;if[j][k]+f[k][i+j-1]+w[j]*w[k]*w[i+j-1])
f[j][i+j-1]=f[j][k]+f[k][i+j-1]+w[j]*w[k]*w[i+j-1];
}
Bign m=MAX;
for(int i=1;i<=n;i++) if(m>f[i][i+n-1]) m=f[i][i+n-1];
m.print();
return 0;
}
括号配对
\(BE\) 中有一类被称为 \(GBE\)。
以下是 \(GBE\) 的定义:
1.空表达式是 \(GBE\)
2.如果表达式 \(A\) 是 \(GBE\),则 \([A]\) 与 \((A)\) 都是 \(GBE\)
3.如果 \(A\) 与 \(B\) 都是 \(GBE\),那么 \(AB\) 是 \(GBE\)
给定 \(BE\) ,问最少添加多少字符可将其变为 \(GBE\)
字符串长度小于100
这个就是很常规的区间\(DP\),感觉没什么好说的
对于 \(f[i][j]\) ,先讨论 \(s[i]\) 与 \(s[j]\) 是否可凑成一对中括号或小括号,如果可以的话 \(f[i][j]=f[i+1][j-1]\)
接着 \(f[i][j]=\mathop{\max}\limits_{k=i}^{j-1} \{ f[i][k]+f[k+1][j]\}\)
#include
#include
#include
#include
#define INF 100007
using namespace std;
const int N = 105;
int n;
int f[N][N];
char s[N];
int main()
{
scanf("%s",s);
n=strlen(s);
for(int i=0;i
分离与合体
原题太长了我要吐槽!!!
杜神牛造了 \(n\) 个区域,他们紧邻着排成一行,编号 \(1…n\) 。在每个区域里都放着一把 \(OI\) 界的金钥匙,每一把都有一定的价值。
一开始 \(LYD\) 可以选择 \(1…n−1\) 中的任何一个区域进入,我们不妨把这个区域记为 \(k\)。进入后 \(LYD\) 会在 \(k\) 区域发生分离,从而分离成两个小 \(LYD\)。分离完成的同时会有一面墙在 \(k\) 区域和 \(k+1\) 区域间升起,从而把 \(1…k\) 和 \(k+1…n\) 阻断成两个独立的区间,并在各自区间内任选除区间末尾之外的任意一个区域再次发生分离,这样就有了四个小小 \(LYD\)……重复以上所叙述的分离,直到每个小 \(LYD\) 发现自己所在的区间只剩下了一个区域,那么他们就可以抱起自己梦寐以求的 \(OI\) 金钥匙。
但是 \(LYD\) 不能就分成这么多个个体存在于世界上,这些小 $ LYD$ 还会再合体,合体的小 $ LYD$ 所在区间中间的墙会消失。合体会获得 (合并后所在区间左右端区域里金钥匙价值之和)\(\times\) (之前分离的时候所在区域的金钥匙价值)。
\(LYD\) 请你编程求出最终可以获得的最大总价值,并按照分离阶段从前到后,区域从左到右的顺序,输出发生分离区域编号。若有多种方案,选择分离区域尽量靠左的方案(也可以理解为输出字典序最小的)。
\(n \leq 300\)
感觉这题难点在于耐下心把题看完……
方程 \(f[i][j]=\mathop{\max}\limits_{k=i}^{j-1} \{ f[i][k]+f[k+1][j]+(a[i]+a[j]) \times a[k] \}\)
因为最后要输出方案,所以每个区域要记录下使 \(f[i][j]\) 取到 \(max\) 的 \(k\)
#include
#include
#include
using namespace std;
const int N = 305;
typedef long long ll;
typedef pair P;
int n;
ll a[N],f[N][N];
int g[N][N];
int head,tail;
P que[N*N];
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
for(int i=2;i<=n;i++)
for(int j=1;j+i-1<=n;j++)
for(int k=j;k
矩阵取数游戏
对于给定的 \(n \times m\) 的矩阵,矩阵中每个元素 \(a_{i,j}\) 均为非负整数。游戏规则如下:
1.每次取数时必须从每行各取走一个元素,共 \(n\) 个,\(m\) 次取完所有元素。
2.每次取走的各个元素只能是该元素所在行行首或行尾。
3.每次取数都有一个的分值,为每行取数得分之和,每行取数得分=被取走元素值 \(\times 2^i\),其中 \(i\) 表示第 \(i\) 次取数,从 1 开始计数。
4.游戏结束时,总得分为 \(m\) 次取数得分之和。
求取数后的最大得分。\(n,m \leq 80, a_{i,j} \leq 10^3\)
这个也没啥难的,每行一次区间\(dp\)
方程 \(f[i][j]=max \{ f[i][j-1] +a[j] \times 2^{m-(j-i+1)+1} , f[i+1][j] +a[i] \times 2^{m-(j-i+1)+1} \}\)
恶心的是又要加高精度。
#include
#include
#include
#include
using namespace std;
const int N = 85;
const int SZ = 10000;
struct Bign{
int s[10],len;
Bign() { len=0; memset(s,0,sizeof(s)); }
Bign operator = (int x){
len=0; memset(s,0,sizeof(s));
if(x==0) s[len++]=0;
while(x!=0) s[len++]=x%SZ,x/=SZ;
return *this;
}
Bign operator + (const Bign &b) const{
Bign c;
for(int i=0,g=0;;i++){
if(i>=len && i>=b.len && g==0) break;
c.s[c.len++]=(s[i]+b.s[i]+g)%SZ;
g=(s[i]+b.s[i]+g)/SZ;
}
return c;
}
Bign operator * (const int &x) const{
Bign c;
for(int i=0,g=0;;i++){
if(i>=len && g==0) { c.len=i; break; }
g=g+x*s[i];
c.s[i]=g%SZ;
g/=SZ;
}
return c;
}
bool operator < (const Bign &b) const{
if(len!=b.len) return len=0;i--)
if(s[i]!=b.s[i]) return s[i]=0;i--){
if(s[i]<10) printf("0");
if(s[i]<100) printf("0");
if(s[i]<1000) printf("0");
printf("%d",s[i]);
}
printf("\n");
}
}mod[N],f[N][N],ans;
int n,m;
int a[N];
int main()
{
scanf("%d%d",&n,&m);
mod[0]=1;
for(int i=1;i<=m;i++) mod[i]=mod[i-1]*2;
ans=0;
for(int x=1;x<=n;x++){
for(int y=1;y<=m;y++)
scanf("%d",&a[y]),f[y][y]=mod[m]*a[y];
for(int i=2;i<=m;i++)
for(int j=1;j+i-1<=m;j++){
f[j][i+j-1]=0;
if(f[j][i+j-1]