第一问考虑动态加边网络流。按照志愿顺序加边,如果能够bfs成功就能成功增广。记录答案即可。
难点在第二问,如何确定最少增长几名呢。二分是比较好想到的。。有一个妙妙的做法就是,在第一问时存下每一个状态的图,然而第二问二分到相应的图,上去增广。小技巧就是不需要的边及时删掉。
这题还是比较妙的,动态加边加上存残图。
// luogu-judger-enable-o2
#include<bits/stdc++.h>
using namespace std;
const int MAXN=500;
const int INF=1e9+7;
int T,C,n,m,s,t,p[MAXN];
struct edge{
int to,w,rev;
edge(int a,int b,int c){
to=a,w=b,rev=c;
}
};
struct Gra{
vector<edge>vec[MAXN];
inline void add(int u,int v,int w){
vec[u].push_back(edge(v,w,vec[v].size()));
vec[v].push_back(edge(u,0,vec[u].size()-1));
}
inline void del(int u,int v){
vec[u].pop_back();
vec[v].pop_back();
}
queue<int>q;
int dep[MAXN];
bool bfs(){
memset(dep,0,sizeof(dep));
dep[s]=1;q.push(s);
while(q.size()){
int u=q.front();q.pop();
for(int i=0;i<vec[u].size();i++){
edge &E=vec[u][i];
int v=E.to;
if(E.w&&!dep[v]){
dep[v]=dep[u]+1;
q.push(v);
}
}
}
if(!dep[t])return 0;
return 1;
}
int dfs(int u,int flow){
if(u==t||flow==0)return flow;
for(int i=0;i<vec[u].size();i++){
edge &E=vec[u][i];
int v=E.to;
if(dep[v]==dep[u]+1&&E.w){
int tmp=dfs(v,min(flow,E.w));
if(tmp){
E.w-=tmp;
vec[v][E.rev].w+=tmp;
return tmp;
}
}
}
return 0;
}
}G[MAXN];
vector<int>Pip[MAXN][MAXN];
int ans[MAXN];
void solve1(){
for(int i=1;i<=n;i++){
G[i]=G[i-1];
G[i].add(s,i,1);
for(int j=1;j<=m;j++){
for(int k=0;k<Pip[i][j].size();k++)G[i].add(i,n+Pip[i][j][k],1);
if(G[i].bfs()){
G[i].dfs(s,INF);
ans[i]=j;
break;
}
for(int k=Pip[i][j].size()-1;k>=0;k--)G[i].del(i,n+Pip[i][j][k]);
}
}
for(int i=1;i<=n;i++){
if(!ans[i])ans[i]=m+1;
printf("%d ",ans[i]);
}
puts("");
}
bool check(int x,int pos){
Gra TG=G[pos-1];
TG.add(s,x,1);
for(int i=1;i<=m;i++){
if(i>p[x])return 0;
for(int j=0;j<Pip[x][i].size();j++)TG.add(x,n+Pip[x][i][j],1);
if(TG.bfs()){
if(i<=p[x])return 1;
return 0;
}
}
return 0;
}
int find(int x){
int l=1,r=x-1,ans=x;
while(l<=r){
int mid=l+r>>1;
if(check(x,x-mid))r=mid-1,ans=mid;
else l=mid+1;
}
return ans;
}
void solve2(){
for(int i=1;i<=n;i++){
if(p[i]>=ans[i])printf("0 ");
else printf("%d ",find(i));
}
puts("");
}
void work(){
for(int i=1;i<=m;i++){
int tmp;
scanf("%d",&tmp);
G[0].add(n+i,t,tmp);
}
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
int tmp;
scanf("%d",&tmp);
if(!tmp)continue;
Pip[i][tmp].push_back(j);
}
}
for(int i=1;i<=n;i++)scanf("%d",&p[i]);
solve1();
solve2();
}
void mem(){
for(int i=0;i<=405;i++){
for(int j=0;j<=405;j++)G[i].vec[j].clear();
}
memset(p,0,sizeof(p));
memset(ans,0,sizeof(ans));
for(int i=0;i<=405;i++)
for(int j=0;j<=405;j++)Pip[i][j].clear();
}
int main(){
//freopen("mentor.in","r",stdin);
//freopen("mentor.out","w",stdout);
scanf("%d%d",&T,&C);
while(T--){
mem();
scanf("%d%d",&n,&m);
s=0,t=n+m+1;
work();
}
return 0;
}
2.林克卡特树
对于题目名字。。2333。很有趣。
10%的数据,直接求带负权的树的直径。记录每个节点往下的最长链和次长链然后瞎算一下。
20%的数据,只删一条边连一条边。我们考虑删完一条边以后答案是什么样的,可以从一个子树中选出一条链,从另一个子树中也选一个链,比较大小。也可以把他们首尾相接。所以直接枚举删边,分别求直径相加即可。
对于60%的数据,通过20%的数据我们想到了删一条边时,加的边是选2个链接起来。更多的删边加边不也是这样嘛。
但有一种情况需要考虑,就是一条链上多个分叉,我们这样是无论如何都接不起来的(成环),所以题目转化为了选k+1条点不相交的链。
f[i][j][k]
f
[
i
]
[
j
]
[
k
]
表示以i为根的子树,已经选了j个链,其中链上的点有
k(0<=k<=2)
k
(
0
<=
k
<=
2
)
条边与点i相交。
转移的时候分 子树顺序转移。其中 g g 是当前的答案, 是已经转移过的答案,这样就不会有冲突了,详细见代码..
代码60pts
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAXN=3e5+10;
const ll INF=1e18+7;
struct edge{
int to,next;ll w;
}e[MAXN<<1];
int head[MAXN],cnt=0,n,k;
inline void add(int u,int v,ll w){
e[++cnt]=(edge){v,head[u],w},head[u]=cnt;
e[++cnt]=(edge){u,head[v],w},head[v]=cnt;
}
int size[MAXN];
ll f[MAXN][105][3],g[105][3];
//f[i][0][0]=0;f[i][1][1]=0;
//f[j][0]=f[k][0..2] k<=j
void dfs(int u,int fa){
f[u][0][0]=f[u][1][1]=0;
size[u]=1;
for(int i=head[u];i;i=e[i].next){
int v=e[i].to;
if(v==fa)continue;
dfs(v,u);
for(int j=0;j<=k;j++)
for(int t=0;t<=2;t++)g[j][t]=-INF;
int limit=min(k,size[u]);
for(int j=0;j<=limit;j++){
for(int t=0;t<=size[v];t++){//此时的f还未被v子树更新。
if(t+j>k+1)break;//两条链连成一个时会-1
for(int tt=0;tt<=2;tt++)
for(int tt2=0;tt2<=2;tt2++)g[t+j][tt]=max(g[t+j][tt],f[u][j][tt]+f[v][t][tt2]);
if(t||j)g[t+j-1][2]=max(g[t+j-1][2],f[u][j][1]+f[v][t][1]+e[i].w);
// if(t||j)g[t+j][2]=max(g[t+j][2],f[u][j][1]+f[v][t][0]+e[i].w);
g[t+j][1]=max(g[t+j][1],f[u][j][0]+f[v][t][1]+e[i].w);//选k个链合并时改变了u节点的度数
// g[t+j][1]=max(g[t+j][1],f[u][j][0]+f[v][t][0]+e[i].w);
}
}
for(int j=0;j<=k;j++)
for(int t=0;t<=2;t++)f[u][j][t]=g[j][t];
size[u]+=size[v];
}
}
int main(){
// freopen("lct.in","r",stdin);
// freopen("lct.out","w",stdout);
scanf("%d%d",&n,&k);
k++;
for(int i=1;i<n;i++){
int u,v;ll w;
scanf("%d%d%lld",&u,&v,&w);
add(u,v,w);
}
for(int i=0;i<=n;i++){
for(int j=0;j<=k;j++)
f[i][j][0]=f[i][j][1]=f[i][j][2]=-INF;
}
dfs(1,1);
cout<<max(f[1][k][0],max(f[1][k][1],f[1][k][2]))<<endl;
}
T3不会写 完