2020 Multi-University Training Contest 2补题

学艺不精,菜鸡落泪。

A. HDOJ-6763 Total Eclipse

题意

给你一个 n n n个点, m m m条边的无向图,第 i i i个点有一个权值 b i b_i bi,你每次要选择可操作的最大的一个连通子图,并将子图上所有点的权值减 1 1 1,求出最小操作次数。

思路

先看正向思路:很明显是选择每一个连通块内最小的点权,并将整个连通块内所有的点都减去这个点权。将操作后 b i = 0 b_i=0 bi=0的点删掉,并对新的连通块重复此操作,记录操作次数即为答案。
但是删点操作过于麻烦,这里要将过程反向,因为点权越大越留到后面,所以按点权降序排序,依次将点加入进图中并建图,用并查集维护连通块,每个连通块内权值最小的点为这个连通块的根。
当加入 x x x时,遍历与 x x x节点相邻的点,若为已经加入进来的点且不与 x x x联通,设这个点所在的连通块的根节点 y y y,则将 x x x y y y联通的贡献为 b y − b x b_y-b_x bybx。这里我们思考一下此处操作的正向含义是什么——我们删掉了 x x x点并且将 x x x所在的连通块断裂成了一个或多个连通块,并且这些点的 b i b_i bi全部减去 b x b_x bx,贡献 b y − b x b_y-b_x bybx就可以理解为:删掉 x x x后,分裂出来的 y y y所在连通块内下一个节点被删的最小操作次数。
完成上述过程后,统计每个最大连通块的根节点的 b b b值,并计入贡献。

举个例子

在这里插入图片描述

代码
//#pragma comment(linker, "/STACK:102400000,102400000")
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int>pii;
//#define int ll
const int maxn=2e5+10,inf=0x3f3f3f3f,mod=1000000007;
int fa[maxn];
int findfa(int x)
{
	if(fa[x]!=x)
		fa[x]=findfa(fa[x]);
	return fa[x];
}
int val[maxn];
vector<int>G[maxn];
bool vis[maxn];
signed main()
{
	std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	#ifdef DEBUG
		freopen("input.in", "r", stdin);
	//	freopen("output.out", "w", stdout);
	#endif
	int t,n,m,u,v;
	cin>>t;
	while(t--)
	{
		cin>>n>>m;
		vector<int>rec;
		for(int i=1;i<=n;i++)
		{
			cin>>val[i];
			G[i].clear();
			vis[i]=0;
			fa[i]=i;
			rec.push_back(i);
		}
		for(int i=1;i<=m;i++)
		{
			cin>>u>>v;
			G[u].push_back(v);
			G[v].push_back(u);
		}
		sort(rec.begin(),rec.end(),[](const int &a,const int &b){
				return val[a]>val[b];
			});
		ll ans=0;
		for(int &u:rec)
		{
			vis[u]=1;//按点权顺序将u加入
			for(int &v:G[u])
			{
				if(!vis[v])
					continue;
				int y=findfa(v);//v节点在之前已加入,找到v节点的根
				if(u==y)
					continue;
				ans+=val[y]-val[u];
				fa[y]=u;//将新加进来的作为连通块根结点,方便得到最小权值
			}
		}
		for(int i=1;i<=n;i++)
			if(fa[i]==i)//最后将每个连通块根节点加上
				ans+=val[i];
		cout<<ans<<endl;
	}
	return 0;
}

E.New Equipments

读完就知道是个最大匹配问题,然后自信满满地写了一发费用流,直接TLE了。
根据二次函数顶点坐标公式 ( − b 2 a , 4 a c − b 2 4 a ) (-\frac{b}{2a},\frac{4ac-b^2}{4a}) (2ab,4a4acb2)可以确定每一个工人 a x 2 + b x + c ax^2+bx+c ax2+bx+c [ 1 , m ] [1,m] [1,m]范围内得到最小值的位置,并且在这个位置向左右延伸出来共 n n n个点(因为其他工人的最优点也可能落在这个位置),因为精度问题,多延伸出来几个建图跑费用流。
因为 d i j k s t r a dijkstra dijkstra单路增广费用流,且每次增广得到的都是此时总流量对应的最小花费,因为有 n n n条边进行限流,每次找到的增广路只能使流量增加1,所以只要在残余网络上跑 n n n d i j k s t r a dijkstra dijkstra就能得到 n n n种匹配的花费。

//#pragma comment(linker, "/STACK:102400000,102400000")
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int>pii;
//#define int ll
const int maxn=2e5+10,inf=0x3f3f3f3f,mod=1000000007;
const ll INF=0x3f3f3f3f3f3f3f3f;
const double eps=1e-9;
//#define DEBUG//#define lowbit(x) ((x) & -(x))//<<setiosflags(ios::fixed)<<setprecision(9)
void read(){}
template<typename T,typename... T2>inline void read(T &x,T2 &... oth) {
	x=0; int ch=getchar(),f=0;
	while(ch<'0'||ch>'9'){if (ch=='-') f=1;ch=getchar();}
	while (ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
	if(f)x=-x;
	read(oth...);
}
//const int maxm = 100010;
struct MCMF
{//复杂度O(n^2f),f为最大流量(我觉得是O(fmlogm)?
	struct Edge {
		ll v, cap, cost, rev;
	};
	const ll INF=0x3f3f3f3f3f3f3f3f;
	ll flow, cost, s, t, n;
	ll dist[maxn], H[maxn], pv[maxn], pe[maxn];
	std::vector<Edge> G[maxn];
	void init(int n){//一定要初始化
		this->n = n;
		for (int i = 0; i <= n; ++i) G[i].clear();
	}
	void addEdge(int u, int v, int cap, ll cost){//dijk费用流中两节点间流向单向
		G[u].push_back({v,cap,cost,G[v].size()});
		G[v].push_back({u,0,-cost,G[u].size()-1});
	}
	bool dijkstra()
	{//将花费作为距离
		std::priority_queue<pair<ll,ll>, std::vector<pair<ll,ll>>, std::greater<pair<ll,ll>> > q;
		std::fill(dist, dist + n + 1, INF);
		dist[s] = 0; q.push({ 0, s });
		while (!q.empty())
		{
			pair<ll,ll> x = q.top(); q.pop();
			ll& u = x.second;
			if (dist[u] < x.first) continue;//不能优化距离
			for (int i = 0; i < G[u].size(); ++i)
			{
				Edge& e = G[u][i];
				ll& v = e.v;
				pair<ll,ll> y(dist[u] + e.cost + H[u] - H[v], v);
				if (e.cap > 0 && dist[v] > y.first)
				{
					dist[v] = y.first;
					pe[v] = i, pv[v] = u;
					q.push(y);
				}
			}
		}
		if (dist[t] == INF)//无增广路
			return false;
		for (int i = 0; i <= n; ++i)
			H[i] += dist[i];
		ll f = INF;
		for (int v = t; v != s; v = pv[v])
			f = std::min(f, G[pv[v]][pe[v]].cap);//记录可增广量
		flow += f;//每次增广一条路径,这条路径增广量就是新增的流量
		cost += f * H[t];//且是此时总流量对应的最小花费
		for (int v = t; v != s; v = pv[v])
		{
			Edge& e = G[pv[v]][pe[v]];
			e.cap -= f;//增广路径上边容量减少
			G[v][e.rev].cap += f;//反向边容量增加
		}
		return true;
	}
	ll solve(int s, int t)
	{
		this->s = s, this->t = t;
		flow = cost = 0;
		std::fill(H, H + n + 1, 0);
		while (dijkstra());//每次选择最小费用增广路径一定是当前残留图的最小增广路径
		return flow;
	}
} mm;
ll a[55],b[55],c[55];
inline ll cost(int ind,int x)
{
	return (ll)a[ind]*x*x+b[ind]*x+c[ind];
}
signed main()
{
	std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	#ifdef DEBUG
		freopen("input.in", "r", stdin);
	//	freopen("output.out", "w", stdout);
	#endif
	int t,n,m;
	scanf("%d",&t);
	while(t--)
	{
		scanf("%d%d",&n,&m);
		for(int i=1;i<=n;i++)//a>0,有最小值>0
			scanf("%lld%lld%lld",&a[i],&b[i],&c[i]);
		set<int>rec;//形成一个二次函数,记录最优的那些点,左右各取n个,因为可能会被占用
		for(int i=1;i<=n;i++)
		{
			int now=min(m,max(1,(int)(-b[i]/(2*a[i])))),cnt=1;
			rec.insert(now);
//			int l=max(1,now-n),r=min(m,now+n+1);
//			for(;l<=r;l++)//左右各延伸n个会TLE
//				rec.insert(l);
			int l=now-1,r=now+1;
            while(cnt<=n+2)
            {
                if(1<=l&&r<=m)
                {
                    if(cost(i,l)<=cost(i,r))
                        rec.insert(l--);
                    else
                        rec.insert(r++);
                }
                else if(r<=m)
                    rec.insert(r++);
                else if(l>=1)
                    rec.insert(l--);
				else
					break;
                cnt++;
            }
		}
		vector<int>v;
		for(auto &x:rec)
			v.push_back(x);
		int st=n+v.size()+1,ed=n+v.size()+2;
		mm.init(ed+10);
		for(int i=1;i<=n;i++)
		{
			mm.addEdge(st,i,1,0);
			for(int j=0;j<v.size();j++)
			{
				mm.addEdge(i,n+j+1,1,cost(i,v[j]));
			}
		}
		for(int j=0;j<v.size();j++)
			mm.addEdge(n+j+1,ed,1,0);
		mm.s=st,mm.t=ed;
		mm.flow=mm.cost=0;
		std::fill(mm.H, mm.H +ed+ 10, 0);
		for(int i=1;i<=n;i++)
		{
			mm.dijkstra();
			printf("%lld%c",mm.cost,(i==n)?'\n':' ');
		}
	}
	return 0;
}

J. HDOJ-6772 New Equipments

没什么好说的,读完就知道是爆搜,卡常过于恶心。用vector不管怎么剪枝都是TLE。

代码
//#pragma comment(linker, "/STACK:102400000,102400000")
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int>pii;
//#define int ll
const int maxn=2e5+10,inf=0x3f3f3f3f,mod=1000000007;
//#define DEBUG//#define lowbit(x) ((x) & -(x))//<<setiosflags(ios::fixed)<<setprecision(9)
void read(){}
template<typename T,typename... T2>inline void read(T &x,T2 &... oth) {
	x=0; int ch=getchar(),f=0;
	while(ch<'0'||ch>'9'){if (ch=='-') f=1;ch=getchar();}
	while (ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
	if(f)x=-x;
	read(oth...);
}
ll ans=0;
struct node
{
	int w[5];
	node(){}
	node(int a,int b,int c,int d)
	{
		w[1]=a;
		w[2]=b;
		w[3]=c;
		w[4]=d;
	}
} arr[55][55];
int n,k,cnt[55],nex[55];
void dfs(int x,ll a,ll b,ll c,ll d)
{
	if(x>k)
	{
		ans=max(ans,a*b*c*d);
		return;
	}
	for(int i=1;i<=cnt[x];i++)
		dfs(nex[x],a+arr[x][i].w[1],b+arr[x][i].w[2],c+arr[x][i].w[3],d+arr[x][i].w[4]);
	return;
}
signed main()
{
	std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	#ifdef DEBUG
		freopen("input.in", "r", stdin);
	//	freopen("output.out", "w", stdout);
	#endif
	int T,t,a,b,c,d;
	scanf("%d",&T);
	while(T--)
	{
		scanf("%d%d",&n,&k);
		for(int i=1;i<=k;i++)
			cnt[i]=0;
		for(int i=1;i<=n;i++)
		{
			scanf("%d",&t);
			cnt[t]++;
			for(int j=1;j<=4;j++)
				scanf("%d",&arr[t][cnt[t]].w[j]);
		}
		int pre=k+1;
		for(int i=k;i>=1;i--)
		{
			if(cnt[i])
			{
				nex[i]=pre;
				pre=i;
			}
		}
		ans=0;
		dfs(pre,100,100,100,100);
		printf("%lld\n",ans);
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值