noi.ac七一模拟赛

打的时候感觉很阴间,期望是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);
}
posted @ 2021-07-07 00:01  小又又yyyy  阅读(32)  评论(0编辑  收藏  举报