1. 程式人生 > 實用技巧 >2020牛客多校第二場01,05題

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);
    }

}