打的时候感觉很阴间,期望是104,但是只打到了84,今天又把T2,T3做了一遍发现还是挺好的
T2
简述题意:
y打败x可以获得x的全部分数并额外获得奖励分数,奖励分数分为两个部分,此次获胜的奖励分数w,和y获胜j次(本次为j+1次)的奖励分数n-j
设Y为一次都没有获胜过的人数,X为所有幸存的人人数(被打败后人消失,一个人不会被打败多次),最大化\(\frac{X^k}{Y}\),\(k\in (1,2)\)
思路:
看数据范围和题目的一些特征应该可以想到网络流,这样问题就在于怎样连边(以下不特殊说明流量全为1)
- 对于第一条,y打败x可以获得x的全部分数
那么就是一条路径的总费用呗,x消失但是费用全加在y身上
- 对于第二条,奖励分数w
网络流的经典套路建虚点,从x向y`连一条费用为w的边
- 对于第三条获胜次数的奖励分数
我们注意到这个奖励是递减的(这个性质是有必要的),根据费用流贪心的想,我一定会先流满赢上一次的,才会流赢这一次的,那么从虚点连n,n-1,……,n-deg的边到终点就好了
-
还有就是起点连向每个人的边,每个人连向终点的边(可能一个人都没打败)
-
最后要处理的就是这个Y了
他很烦人,我们考虑他的补集:多少个人打败过别人,这也不好求,那我们直接枚举他
怎么处理呢?建一个虚终点,从每个人的虚点向这个虚终点连一条n+inf的边,然后从虚终点向终点连一条容量为y的边
这样再求最大费用的时候一定会优先走几条n+inf的边直到容量y全部流满
这样如果虚终点向终点的边的反边流满的话,就证明可以有y个人打败过别人,且现在的X最大,直接求解就好啦
感觉这个套路和思想还是要熟悉一下的
代码:
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <queue>
#define int long long
#define B cout<<"Breakpoint"<<endl;
#define O(x) cout<<#x<<" "<<x<<endl;
#define o(x) cout<<#x<<" "<<x<<" ";
using namespace std;
int read(){
int x = 1,a = 0;char ch = getchar();
while (ch < ‘0‘||ch > ‘9‘){if (ch == ‘-‘) x = -1;ch = getchar();}
while (ch >= ‘0‘&&ch <= ‘9‘){a = a*10+ch-‘0‘;ch = getchar();}
return x*a;
}
const int maxn = 1e5+10,inf = 1e18+7;
int n,m,k;
struct node{
int u,v,to,nxt,w,lim;
}ed[maxn << 1],edge[maxn << 1];
int head[maxn],tot = 1,cnt;
void add(int u,int to,int lim,int w){
ed[++tot].w = w;
ed[tot].to = to;
ed[tot].nxt = head[u];
ed[tot].lim = lim;
head[u] = tot;
}
int s,t,t0,sum,a[maxn];
int dis[maxn],cur[maxn],vis[maxn],pre1[maxn],pre2[maxn];
bool SPFA(){
memset(vis,0,sizeof(vis));
for (int i = s;i <= t;i++) dis[i] = -inf;
queue<int> q;q.push(s);
dis[s] = 0,vis[s] = 1;
while (!q.empty()){
int x = q.front();q.pop();
vis[x] = 0;
for (int i = head[x];i;i = ed[i].nxt){
int to = ed[i].to;
if (!ed[i].lim) continue;
if (dis[to] < dis[x]+ed[i].w){
dis[to] = dis[x]+ed[i].w;
pre1[to] = x,pre2[to] = i;
if (!vis[to]){
vis[to] = 1;
q.push(to);
}
}
}
}
return dis[t] != -inf;
}
int deg[maxn];
void add_ed(int u,int v,int lim,int w){
edge[++cnt].u = u,edge[cnt].v = v,edge[cnt].lim = lim,edge[cnt].w = w;
}
void init(){
memset(head,0,sizeof(head));
for (int i = 2;i <= tot;i++) ed[i].to = ed[i].nxt = ed[i].lim = ed[i].w = 0;
tot = 1;
for (int i = 1;i <= cnt;i++) add(edge[i].u,edge[i].v,edge[i].lim,edge[i].w);
}
double ans;
signed main(){
n = read(),m = read(),k = read();
for (int i = 1;i <= n;i++) a[i] = read(),sum += a[i];
s = 0,t0 = n*2+1,t = n*2+2;
for (int i = 1;i <= n;i++) add_ed(s,i,1,0),add_ed(i,s,0,0);
for (int i = 1;i <= n;i++) add_ed(i,t,1,0),add_ed(t,i,0,0);
for (int i = 1;i <= m;i++){
int x = read(),y = read(),w = read();
deg[y]++;
add_ed(x,y+n,1,w),add_ed(y+n,x,0,-w);
}
for (int i = 1;i <= n;i++){
add_ed(i+n,t0,1,inf+n),add_ed(t0,i+n,0,-inf-n);
for (int j = 2;j <= deg[i];j++){
add_ed(i+n,t,1,n-j+1),add_ed(t,i+n,0,j-n-1);
}
}
for (int i = 1;i < n;i++){
init();
add(t0,t,i,0),add(t,t0,0,0);
int res = 0;
while (SPFA()){
int tmp = inf;
for (int i = t;i != s;i = pre1[i]) tmp = min(tmp,ed[pre2[i]].lim);
res += tmp*dis[t];
for (int i = t;i != s;i = pre1[i]) ed[pre2[i]].lim -= tmp,ed[pre2[i]^1].lim += tmp;
}
// cout<<res<<endl;
if (ed[tot].lim == i){
if (k == 1) ans = max(ans,1.0*(res+sum-1ll*i*inf)/(n-i));
if (k == 2) ans = max(ans,1.0*(res+sum-1ll*i*inf)*(res+sum-1ll*i*inf)/(n-i));
}
else break;
}
printf("%.10lf",ans);
return 0;
}
T3
借着这道题我也想好好的思考一下决策单调性优化dp,感觉自己再分治这块一直都是弱项,比如整体二分,CDQ分治什么的,最近再好好练练
怎么发现决策单调性呢?我们可以打表或者感性理解
决策单调性的概念
假如对于某一个dp方程,dp(i)的最优转移是dp(k),那么称k为i的决策点
而dp方程满足决策单调性指的是,决策点k随着i的增大保持单调不减
1. 被决策点不会成为决策点
一般的遇到这种情况时,dp方程有两维
比如,dp(i,j)表示在第i个阶段、对j做决策,dp(i,j)由dp(i?1,k)转移得来
void solve(int x,int l,int r,int nl,int nr){
if (l > r) return;
int mid = (l+r >> 1),pos;
for (int i = nl;i <= min(mid-1,nr);i++){
int val = dp[x-1][i]+w(i,mid);
if (val < dp[x][mid]) dp[x][mid] = val,pos = j;
}
solve(x,l,mid-1,nl,pos),solve(x,mid+1,r,pos,nr);
}
2. 被决策点可能会成为决策点
很简单,我们分治套分治就好了,因为cdq分治的时候我们就是在考虑[l,mid]对[mid+1,r]的贡献,所以我们这样是很符合分治的思想的
void solve(int l,int r,int L,int R){
if (l > r||L > R) return;
int pos,lst = inf,w = 0;
for (int i = L;i <= R;i++) if ((w = calc(i,mid)) < lst) lst = w,pos = i;
dp[mid] = min(dp[mid],lst);
solve(l,mid-1,L,pos);solve(mid+1,r,pos,R);
}
void cdq(int l,int r){
if (l == r) return;
cdq(l,mid);solve(mid+1,r,l,mid);cdq(mid+1,r);
}