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