学艺不精,菜鸡落泪。
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
by−bx。这里我们思考一下此处操作的正向含义是什么——我们删掉了
x
x
x点并且将
x
x
x所在的连通块断裂成了一个或多个连通块,并且这些点的
b
i
b_i
bi全部减去
b
x
b_x
bx,贡献
b
y
−
b
x
b_y-b_x
by−bx就可以理解为:删掉
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,4a4ac−b2)可以确定每一个工人
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;
}