2020牛客多校第二场01,05题

05:

New Equipments

链接:http://acm.hdu.edu.cn/showproblem.php?pid=6767

此题害人呀,建图是个技术活,万万没想到,我铁骨铮铮把图建,到头还栽建图上。

注意到m非常大,所以如果我们把每个n和每个m直接进行相连的话,肯定是过大的。题意要求我们的是每个人一个机器,那么我们每个人都与n个费用最小的机器进行相连,那就可以保证是完备匹配了。

所以,我们需要对每个人i找到函数ai*j*j+bi*j+c在1-m上的前n个最小值。

因为是一元二次函数,所以最小值的横坐标就是-(b/(a*2));但是注意到这有可能是个小数,所以我们向下取整得x,然后x和x+1在比较一下y值,可得到最小值的x值。

需要注意,算出来的x值有可能不在1-m的范围内,所以还需要比较判断一下。

建图完成之后,直接跑spfa就行。

此题,一直以为自己spfa错了,结果发现还是建图的问题。找bug找了一天呀    哭了。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#include"set"
#include"map"
using namespace std;
typedef long long ll;

inline int read(){
    int s = 0, w = 1; char ch = getchar();
    while(ch < '0' || ch > '9')   { if(ch == '-') w = -1; ch = getchar(); }
    while(ch >= '0' && ch <= '9') { s = (s << 3) + (s << 1) + (ch ^ 48); ch = getchar(); }
    return s * w;
}
const ll inf=~0ULL>>1;
const int N=55,M=N*N+N+7,E=500005;
int ver[E], edge[E], Next[E], head[E];
ll cost[E],d[M];
int incf[M], pre[M], v[M];
int n, k, tot, s, t, maxflow,m,q[E];
ll ans,l,r;
ll a[N],b[N],c[N];
set<int> G[N],ALL;
map<int,int>ID;

void init(){
    for(int i = 0; i <= n; i ++){G[i].clear();}
    ALL.clear();ID.clear();
}
void add(int x, int y, int z, ll c) {
	// 正向边,初始容量z,单位费用c
	ver[++tot] = y, edge[tot] = z, cost[tot] = c;
	Next[tot] = head[x], head[x] = tot;
	// 反向边,初始容量0,单位费用-c,与正向边“成对存储”
	ver[++tot] = x, edge[tot] = 0, cost[tot] = -c;
	Next[tot] = head[y], head[y] = tot;
}


bool spfa() {
	queue<int> q;
	for(int i = 0; i <= t; i ++) {
        d[i] = inf; // INF
	    v[i] = 0;
	}
	q.push(s); d[s] = 0; v[s] = 1; // SPFA 求最长路
	incf[s] = 1LL << 30; // 增广路上各边的最小剩余容量
	while (q.size()) {
		int x = q.front(); v[x] = 0; q.pop();
		for (int i = head[x]; i; i = Next[i]) {
			if (!edge[i]) continue; // 剩余容量为0,不在残量网络中,不遍历
			int y = ver[i];
			if (d[y]>d[x] + cost[i]) {
				d[y] = d[x] + cost[i];
				incf[y] = min(incf[x], edge[i]);
				pre[y] = i; // 记录前驱,便于找到最长路的实际方案
				if (!v[y]) v[y] = 1, q.push(y);
			}
		}
	}
	if (d[t] == inf) return false; // 汇点不可达,已求出最大流
	return true;
}

// 更新最长增广路及其反向边的剩余容量
void update() {
	int x = t;
	while (x != s) {
		int i = pre[x];
		edge[i] -= incf[t];
		edge[i ^ 1] += incf[t]; // 利用“成对存储”的xor 1技巧
		x = ver[i ^ 1];
	}
	maxflow += incf[t];
	ans += d[t];
}
inline ll cal(ll a,ll b,ll x){return a*x*x+b*x;}
inline set<int> extend(ll a,ll b){
  ll tmp=-(b/(a*2));
  tmp-=1;
  tmp=max(tmp,1LL);
  tmp=min(tmp,1LL*m);
  while(tmp<m&&cal(a,b,tmp)>cal(a,b,tmp+1))tmp++;
  ll l=tmp,r=tmp+1;
  set<int>ret;
  ret.clear();
  for(int i=1;i<=n;i++){
    if(l<1){
      ret.insert(r++);
      continue;
    }
    if(r>m){
      ret.insert(l--);
      continue;
    }
    if(cal(a,b,l)<cal(a,b,r))ret.insert(l--);else ret.insert(r++);
  }
  for(set<int>::iterator it=ret.begin();it!=ret.end();it++)ALL.insert(*it);
  return ret;
}
int main() {
    int T = read();
    while(T --){
        n = read(); m = read();
        init();
        tot = 1;
        for(int i = 1; i <= n; i ++){
            scanf("%lld%lld%lld",&a[i],&b[i],&c[i]);
            G[i]=extend(a[i],b[i]);
        }
        int top1 = 0;
        for(set<int> :: iterator it = ALL.begin(); it != ALL.end(); it ++){
            int x = *it;
            ID[x] = ++ top1;
        }
        t = n + top1 + 2;
        s =  n + top1 + 1;
        for(int i = 0; i <= t;i ++){
            head[i] = 0;incf[i] = 0;pre[i] = 0;
        }
        for(int i = 1; i <= n; i ++){
            for(set<int> :: iterator it = G[i].begin(); it != G[i].end(); it ++){
                int x = *it;
                ll f = a[i] * x * x + b[i] * x + c[i];
                add(i,ID[x] + n,1,f);
            }
        }

        for(int i = 1; i <= n; i ++){
            add(s,i,1,0);
        }
        for(int i = 1; i <= top1; i ++){
            add(i + n,t,1,0);
        }
        maxflow = ans = 0;
        int x;
        for(int i = 1; i <= n; i ++) {
         spfa();update();
         printf("%lld%c",ans,i<n?' ':'\n');
        }
    }

}

  01:

Total Eclipse

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=6763

此题直观想法就是找最大连通块,全部-1,在找最大连通块。

但是这样不好实现代码,所以我们可以考虑倒着来;

我们按亮度值,从大到小进行排序,然后依次加入并查集。

加入每个点 x 时遍历与 x 相连的所有边 (x, y),如果 y 在 x 之前加入且 x 和 y 不连通则将 x
和 y 合并,并将 y 所在连通块的树根的父亲设为 x。
那么我们可以发现每个点变成0,需要做的贡献是a[i] - a[fa[i]];
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#include"set"
#include"map"
using namespace std;
#define inf 1e9+7
typedef long long ll;
inline int read(){
    int s = 0, w = 1; char ch = getchar();
    while(ch < '0' || ch > '9')   { if(ch == '-') w = -1; ch = getchar(); }
    while(ch >= '0' && ch <= '9') { s = (s << 3) + (s << 1) + (ch ^ 48); ch = getchar(); }
    return s * w;
}

const int N = 100010, M = 200100;
int n,m,b[N],a[N];
int f[N],fa[N],vis[N];
int head[N],ver[M << 1],Next[M << 1],tot;

void init(){
    tot = 0;
    for(int i = 0; i <= n; i ++){
        f[i] = i; vis[i] = 0;head[i] = 0;
        fa[i] = 0;
    }
}
void add(int x,int y){
    ver[++ tot] = y; Next[tot] = head[x]; head[x] = tot;
}
int Find(int x){
    if(x == f[x]) return x;
    return f[x] = Find(f[x]);
}
int cmp(int x,int y){
    return b[x] > b[y];
}
int main() {
    int T = read();
    while(T --){
        n = read(); m = read();
        init();
        for(int i = 1; i <= n; i ++) {b[i] = read();a[i] = i;}
        for(int i = 1; i <= m; i ++){
            int x = read(),y = read();
            add(x,y); add(y,x);
        }
        sort(a + 1,a + n + 1,cmp);
        for(int i = 1; i <= n; i ++){
            int x = a[i];
            vis[x] = 1;
            for(int j = head[x]; j ; j = Next[j]){
                int y = ver[j];
                if(vis[y] == 0) continue;
                y = Find(y);
                if(y == x) continue;
                f[y] = x; fa[y] = x;
            }
        }
        ll ans = 0;
        for(int i = 1; i <= n; i ++)
            ans += b[i] - b[fa[i]];
        printf("%lld\n",ans);
    }

}

  

posted @ 2020-07-24 22:24  风生  阅读(99)  评论(0编辑  收藏  举报