Sue的小球
题目大意
有 (n) 个小球在下落,初始位置 ((x_i,y_i)),下落速度为 (v_i)。你初始位置在 (x_0),速度为 (1)。我们定义一个小球的贡献为接到它的时候它的纵坐标。问接到每个小球(接只接一次)的贡献之和的最大值。
(nle 10^3)。
思路
算是为dp开了另外一种思路。
首先你发现,我们贪心一下,肯定每次选没选且最靠近当前位置的小球,具体证明不是很难。形式化一下,就是说按横坐标排序之后,假设已经接到 (lsim r),那么下次一定接 (l-1) 或者 (r+1)。
于是,我们可以设 (dp_{l,r,0/1}) 表示接到 (lsim r) 这段区间当前是在 (l/r) 时的小球的最大贡献。然后你发现这样很合理,但是你不好进行转移,因为你不能算出当前的时间。
但是转念一想,我们发现,如果我们能够在计算每个小球就把当前没有接到的小球消失的贡献都去掉的话就没有这种问题了。于是我们可以设 (w_{l,r}=sum_{i=1}^n v_i-sum_{i=l}^{r}v_i),那么我们可以得到转移式:
(dp_{l,r,1}) 同理。时间复杂度 (Theta(n^2))。
( exttt{Code})
#include <bits/stdc++.h>
using namespace std;
#define Int register int
#define MAXN 1005
struct node{
int x,y,v;
bool operator < (const node &p)const{return x < p.x;}
}fuck[MAXN];
int n,x0,w[MAXN][MAXN],f1[MAXN][MAXN],f2[MAXN][MAXN];
template <typename T> inline void read (T &t){t = 0;char c = getchar();int f = 1;while (c < '0' || c > '9'){if (c == '-') f = -f;c = getchar();}while (c >= '0' && c <= '9'){t = (t << 3) + (t << 1) + c - '0';c = getchar();} t *= f;}
template <typename T,typename ... Args> inline void read (T &t,Args&... args){read (t);read (args...);}
template <typename T> inline void write (T x){if (x < 0){x = -x;putchar ('-');}if (x > 9) write (x / 10);putchar (x % 10 + '0');}
int Abs (int x){return x > 0 ? x : -x;}
signed main(){
read (n,x0);int tot = 0;
for (Int i = 1;i <= n;++ i) read (fuck[i].x);
for (Int i = 1;i <= n;++ i) read (fuck[i].y);
for (Int i = 1;i <= n;++ i) read (fuck[i].v),tot += fuck[i].v;
sort (fuck + 1,fuck + n + 1);
memset (f1,0xcf,sizeof (f1));
memset (f2,0xcf,sizeof (f2));
for (Int i = 1;i <= n;++ i)
for (Int j = i,sum = 0;j <= n;++ j) sum += fuck[j].v,w[i][j] = tot - sum;
for (Int i = 1;i <= n;++ i) f1[i][i] = f2[i][i] = fuck[i].y - Abs (x0 - fuck[i].x) * tot;
for (Int i = n;i >= 1;-- i)
for (Int j = i + 1;j <= n;++ j)
f1[i][j] = max (f1[i][j],fuck[i].y + f1[i + 1][j] - (fuck[i + 1].x - fuck[i].x) * w[i + 1][j]),
f1[i][j] = max (f1[i][j],fuck[i].y + f2[i + 1][j] - (fuck[j].x - fuck[i].x) * w[i + 1][j]),
f2[i][j] = max (f2[i][j],fuck[j].y + f1[i][j - 1] - (fuck[j].x - fuck[i].x) * w[i][j - 1]),
f2[i][j] = max (f2[i][j],fuck[j].y + f2[i][j - 1] - (fuck[j].x - fuck[j - 1].x) * w[i][j - 1]);
printf ("%.3f
",max (f1[1][n],f2[1][n]) / 1000.0);
return 0;
}
名次排序问题
题目大意
给出一个 (n) 个点的序列 (a_{1,2,...,n}),每次可以把 (i) 换到 (j),贡献是 (i+j) ,问最后变成一个单调下降序列的最小贡献时的交换次数。
(nle 10^3),保证 (a_i) 互不相等。
思路
luogu上没有,如果有的话一定是个黑题吧。
首先我们可以转移题意,转换成一个权值 (in [1,n]) 的序列,变为 (1sim n) 的最小贡献。
然后你发现这个东西有以下 (2) 个性质:
-
每个点只会交换一次
-
交换一定权值从大到小进行
第一个显然,第二个可以靠猜(反正我猜中了)或者感性理解。
于是我们得到一个几个推论:
-
对于每一个数 (x),你在移动时有两种选择,1、 移动到数 (x+1) 前面,或者如果 (x) 的位置小于 (x+1) 的位置,那么可以不移动,等待后面 (x) 到 (x+1) 之间的数都移动走。
-
对于每一个数 (x),比它小的数相对位置不变。(相对位置就是指谁在前谁在后)
-
对于每一个数 (x),比 (x+1) 大的数都一定在 (x+1) 后面。
后面 2 个推论可以从第 1 个推出来,这里就不讲了。(因为跟主旨无关(是不是该夸夸我详略得当(伦敦雾
但是我们发现我们光知道相对位置没有什么用啊,我们得知道确切位置才可以啊。其实知道相对位置是做得到的。
我们发现对于一个数 (x) ,如果 (x+1sim n) 都移动完了,那么当前位置其实就是 (1sim pos_x) 中比 (x) 小的数 (+1) ,其中 (pos_x) 就是 (x) 在原序列的位置。这个可以从推论 2 看出来。
然后你发现还是不好搞,于是我们修改一下规则,把移动 (i o j) 变为 (i) 加入 (j) 所在的点。然后你发现这样做的话,你就不会移动点了,而且点之间的相对位置也不会改变。(具体例子可以看论文)
接着我们设 (c(p,x)) 表示原序列中 (1sim p) 中比 (x) 小的数 (+1)。然后我们发现如果 (pos_i<j) ,把 (i) 移动到 (j) 的贡献其实就是 (c(pos_i,i)+c(j,i+1)-1)((-1) 是因为移动到 (j-1) 前面)。(pos_i>j) 同理。
于是我们考虑设 (dp_{i,j}) 表示已经考虑了 (i+1sim n),且点 (i) 移动到 (j) 的最小贡献。对于第1中选择,转移式就是:
这里的式子跟上面不一样是因为这个式子归纳了 (pos_i>j) 的情况。
我们还需要考虑第2中情况,即它不变时的方案。注意到这篇论文的内容 我们发现我们需要考虑当前不动对后面的影响。你发现对于中间( (x) 和移动到的点之间)未考虑的点 (i) 它一定会移动到 (x) 的前面去,那么轮到 (i) 的时候 (x) 的前面已经排了 (x-1,x-2,...,i+1),所以它还需要额外算上 (x-i),剩下的部分会在上面考虑到。对于移动到的点之后的点肯定在轮到移动到的点的时候就考虑到了。
于是转移式就是:
((a_k) 是序列第 (k) 个)
于是我们就可以在 (Theta(n^2)) 的时间复杂度内解决这个问题了。
不过我们要求最后的答案的话我们需要统计一下每一个点移动的点,具体可以见代码。
( exttt{Code})
#include <bits/stdc++.h>
using namespace std;
#define Int register int
#define INF 0x7f7f7f7f
#define MAXN 1005
int n,tot,pos[MAXN],dp[MAXN][MAXN],pre[MAXN];
struct node{
int x,ind;
}fuck[MAXN];
bool cmp1 (node a,node b){return a.x > b.x;}
bool cmp2 (node a,node b){return a.ind < b.ind;}
template <typename T> inline void read (T &t){t = 0;char c = getchar();int f = 1;while (c < '0' || c > '9'){if (c == '-') f = -f;c = getchar();}while (c >= '0' && c <= '9'){t = (t << 3) + (t << 1) + c - '0';c = getchar();} t *= f;}
template <typename T,typename ... Args> inline void read (T &t,Args&... args){read (t);read (args...);}
template <typename T> inline void write (T x){if (x < 0){x = -x;putchar ('-');}if (x > 9) write (x / 10);putchar (x % 10 + '0');}
void dfs (int i,int j){
if (i == n) return ;
if (pos[i] != j) tot ++,dfs (i + 1,j);
else dfs (i + 1,pre[i]);
}
signed main(){
read (n);
for (Int i = 1;i <= n;++ i) read (fuck[i].x),fuck[i].ind = i;
sort (fuck + 1,fuck + n + 1,cmp1);
for (Int i = 1;i <= n;++ i) pos[i] = fuck[i].ind,fuck[i].x = i;
sort (fuck + 1,fuck + n + 1,cmp2);
memset (dp,0x7f,sizeof (dp));
++ n,dp[n][n] = 0,fuck[n].x = n;
for (Int i = n - 1;i;-- i){
int v1 = 1,v2 = 1;
for (Int j = 1;j < pos[i];++ j) v1 += (fuck[j].x < i);
for (Int j = 1;j <= n;++ j) v2 += (fuck[j].x < i),dp[i][j] = min (dp[i][j],dp[i + 1][j] + v1 + v2);
for (Int j = pos[i] + 1,tmp = 0;j <= n;++ j){
if (dp[i + 1][j] + tmp < dp[i][pos[i]]) dp[i][pos[i]] = dp[i + 1][j] + tmp,pre[i] = j;
if (fuck[j].x < i) tmp += i - fuck[j].x;
}
}
int ans = 0x7f7f7f7f,pos = 0;
for (Int i = 1;i <= n;++ i) if (dp[1][i] < ans) ans = dp[1][i],pos = i;
dfs (1,pos),write (tot),putchar ('
');
return 0;
}
参考博客
https://www.cnblogs.com/yimmortal/p/10160630.html 建议与论文一起服用
https://blog.csdn.net/icefox_zhx/article/details/80014541 可以看一下代码实现(或者看我的也可以)
方块消除
题目大意
有一个数列,每次可以消掉一掉连续的颜色相同的一块,贡献就是 (x^2),其中 (x) 是长度。问最大贡献。
思路
不知道如何去讲思考过程,所以直接讲方法?
我们可以设 (f_{l,r,i}) 表示消掉 (l,r) 并且假设在 (r) 的右边存在一个长度为 (i) 并颜色与 (r) 相同的块的最大贡献。于是我们可以得到转移式:
显然,想一下就明白了。最后答案就是 (f_{1,n,0})
( exttt{Code})
#include <bits/stdc++.h>
using namespace std;
#define Int register int
#define MAXN 205
template <typename T> inline void read (T &t){t = 0;char c = getchar();int f = 1;while (c < '0' || c > '9'){if (c == '-') f = -f;c = getchar();}while (c >= '0' && c <= '9'){t = (t << 3) + (t << 1) + c - '0';c = getchar();} t *= f;}
template <typename T,typename ... Args> inline void read (T &t,Args&... args){read (t);read (args...);}
template <typename T> inline void write (T x){if (x < 0){x = -x;putchar ('-');}if (x > 9) write (x / 10);putchar (x % 10 + '0');}
int sqr (int x){return x * x;}
int n,col[MAXN],pre[MAXN],len[MAXN],dp[MAXN][MAXN][MAXN];
signed main(){
read (n);
for (Int i = 1;i <= n;++ i) read (col[i]);
for (Int i = 1;i <= n;++ i) read (len[i]),pre[i] = pre[i - 1] + len[i];
for (Int leng = 1;leng <= n;++ leng){
for (Int l = 1;l + leng - 1 <= n;++ l){
int r = l + leng - 1;
for (Int i = 0;i <= pre[n] - pre[r];++ i) dp[l][r][i] = dp[l][r - 1][0] + sqr (len[r] + i);
for (Int i = l;i <= r - 1;++ i)
for (Int j = 0;j <= pre[n] - pre[r];++ j)
if (col[i] == col[r]) dp[l][r][j] = max (dp[l][r][j],dp[l][i][len[r] + j] + dp[i + 1][r - 1][0]);
}
}
write (dp[1][n][0]),putchar ('
');
return 0;
}
奥运物流
题目大意
有一个 (n) 个点的图,给出参数 (k),每个点有一个后继,每个点有两个参数,分别为 (R(x),C(x)),其中 (C(x)) 是给定参数,(R(x)) 由 (C(x)) 计算,公式为:
现在你有 (m) 次修改机会,每次可以更改一个点的后继。求 (R(1)) 的最大值。
思路
下面定义 (d(i)) 表示 (i o 1) 的距离。
首先我们发现这个图一定是一个森林,就是很多棵树,然后树的根构成一个环。
我们发现对于对于一个点 (x),他对 (R(1)) 的贡献就是 (C(x) imes k^{d(x)}/(1-k^{len})),其中 (len) 是环上点的数量。具体说明可以分类讨论,分别考虑环上的点以及树上的点来说明。
然后我们如果枚举环的长度的话,那么我们就只需要让 (sum_{x=1}^{n}C(x) imes k^{d(x)}) 最大。这个问题就类似于没有上司的舞会。
我们考虑设计状态 (dp_{u,i,d}) 表示以 (u) 为根的子树改变了 (i) 次,点 (i) 深度为 (d) 的最大贡献。于是,我们可以得到转移式:
转移显然。
最后再把环上的点都合并起来就行了。在实现的时候为了方便我们可以直接破环,把整个图变成一棵树,以 (1) 为根做上述 ( ext{dp}) 即可。
时间复杂度最坏 (Theta(n^5)) (假设 (n,m) 同阶)
( exttt{Code})
#include <bits/stdc++.h>
using namespace std;
#define double long double
#define Int register int
#define MAXN 65
template <typename T> inline void read (T &t){t = 0;char c = getchar();int f = 1;while (c < '0' || c > '9'){if (c == '-') f = -f;c = getchar();}while (c >= '0' && c <= '9'){t = (t << 3) + (t << 1) + c - '0';c = getchar();} t *= f;}
template <typename T,typename ... Args> inline void read (T &t,Args&... args){read (t);read (args...);}
template <typename T> inline void write (T x){if (x < 0){x = -x;putchar ('-');}if (x > 9) write (x / 10);putchar (x % 10 + '0');}
int n,m,s[MAXN];
vector <int> G[MAXN];
double k,c[MAXN],pw[MAXN],tmp[MAXN][MAXN],dp[MAXN][MAXN][MAXN];
void dfs (int u,int dep,int up){
for (Int i = 0;i <= up;++ i) for (Int j = 0;j <= dep;++ j) dp[u][i][j] = c[u] * pw[j];
for (Int v : G[u]){
dfs (v,dep + 1,up);
memset (tmp,0,sizeof (tmp));
for (Int i = 0;i <= up;++ i)
for (Int j = 0;i + j <= up;++ j)
for (Int d = 0;d <= dep;++ d){
tmp[i + j][d] = max (tmp[i + j][d],dp[u][i][d] + dp[v][j][d + 1]);
if (j > 0) tmp[i + j][d] = max (tmp[i + j][d],dp[u][i][d] + dp[v][j - 1][1]);
}
for (Int i = 0;i <= up;++ i) for (Int j = 0;j <= dep;++ j) dp[u][i][j] = tmp[i][j];
}
}
double calc (int up){
dfs (1,0,up);
return dp[1][up][0];
}
signed main(){
read (n,m);scanf ("%Lf",&k);
pw[0] = 1;for (Int i = 1;i <= n;++ i) pw[i] = pw[i - 1] * k;
for (Int i = 1;i <= n;++ i) read (s[i]);
for (Int i = 1;i <= n;++ i) scanf ("%Lf",&c[i]);
double ans = 0;
for (Int d = 2,p = s[1];p != 1;++ d,p = s[p]){
for (Int i = 1;i <= n;++ i) G[i].clear ();
int t = s[p];s[p] = 1;
for (Int i = 2;i <= n;++ i) G[s[i]].push_back (i);
ans = max (ans,calc (m - (t != 1)) / (1 - pw[d]));
s[p] = t;
}
printf ("%.2Lf
",ans);
return 0;
}