Codeforces Round 1307 简要题解

A. Cow and Haybales

B. Cow and Friend

C. Cow and Message

D. Cow and Fields

E. Cow and Treats

注意到两边牛吃的范围不能相交,并且因为不能跨越,所以两边分别不能选喜好相同的牛。然后可以发现一个方案合法当且仅当分界点两侧的牛忽略其他奶牛可以吃饱(因为可以按要吃的草距离从大到小的顺序安排牛去吃)。
那么就很简单了。考虑枚举左侧牛吃到的最右的草 i i i,此时每种喜好的奶牛就独立了,可以分别做一个简单的贪心和计数,注意到若 i > 0 i>0 i>0,要求选择一只喜好 s i s_i si的牛吃掉这堆草。
枚举 i i i后可以暴力枚举所有牛,时间复杂度为 O ( n ( n + m ) ) \mathcal O(n(n+m)) O(n(n+m))

#include 
#define MOD 1000000007

using namespace std;

typedef long long ll;

vector <int> vt[5005];
int num[5005],size1[5005],size2[5005];

int main() {
  int n,m;
  scanf("%d%d",&n,&m);
  for(int i=1;i<=n;i++) scanf("%d",&num[i]);
  for(int i=1;i<=m;i++) {
  	int x,y;
  	scanf("%d%d",&x,&y);
  	vt[x].push_back(y);
  }
  for(int i=1;i<=n;i++) sort(vt[i].begin(),vt[i].end());
  int ans1=0,ans2=0;
  for(int i=0;i<=n;i++) {
  	memset(size1,0,sizeof(size1));
  	memset(size2,0,sizeof(size2));
  	for(int j=1;j<=i;j++) size1[num[j]]++;
  	for(int j=i+1;j<=n;j++) size2[num[j]]++;
  	int s1=0;
  	ll s2=1;
  	bool v=1;
  	for(int j=1;j<=n;j++) {
  		int r1=0,r2=0;
  		while (r1<vt[j].size()&&vt[j][r1]<=size1[j]) r1++;
  		while (r2<vt[j].size()&&vt[j][r2]<=size2[j]) r2++;
  		if (num[i]==j) {
			int sz=0;
			for(int k=0;k<vt[j].size();k++)
			  if (vt[j][k]==size1[j]) sz++;
			if (!sz) {
				v=0;
				break;
			}
			r2-=(size1[j]<=size2[j]);
			if (r2) {
				s1+=2;
				s2=s2*sz%MOD*r2%MOD;
			}
			else {
				s1++;
				s2=s2*sz%MOD;
			}
		  }
		else {
			if (r1<r2) swap(r1,r2);
			if (r1>1&&r2) {
				s1+=2;
				s2=s2*r2%MOD*(r1-1)%MOD;
			} 
			else if (r1) {
				s1++;
				s2=s2*(r1+r2)%MOD;
			}
		}
	  }
	if (!v) continue;
	if (s1>ans1) {
		ans1=s1;
		ans2=s2;
	}
	else if (s1==ans1) ans2=(ans2+s2)%MOD;
  }
  printf("%d %d\n",ans1,ans2);
  return 0;
}

F. Cow and Vacation

考虑把每条边取中点拆成两条,这样每次就可以走 2 k 2k 2k步了。注意到如果存在一个点 x x x,与休息点 u u u v v v距离都不超过 k k k,就可以从 u u u走到 v v v了。
那么可以考虑从每个休息点开始BFS出距离不超过 k k k的所有点,如果BFS过程中与其他休息点BFS到的点相交了就可以把两个休息点合并起来,用并查集维护即可。这样会得到若干个连通块,满足连通块内部任意点互相可达(进入连通块之前和出去之后甚至分别可以走不超过 k k k步不用休息),但是两个连通块内部的休息点互相不可达。
考虑一个询问 ( a , b ) (a,b) (a,b),如果 d i s ( a , b ) ≤ 2 k dis(a,b)\leq 2k dis(a,b)2k直接合法,否则考虑分别从 a a a b b b k k k步到达 u u u,从 b b b a a a k k k步到达 v v v,合法当且仅当 u u u v v v在同一个连通块内。这个充分性显然,必要性考虑你走的过程一定是先从 a a a走到一个连通块,再出来走到 b b b,其中进入之前和出去之后都不会休息,那么由于必须经过 u u u v v v,并且 u u u v v v不可能在连通块外(否则再走 k k k步也没法休息),因此 u u u v v v必须在同个连通块。
用倍增预处理的话时间复杂度为 O ( ( n + q ) log ⁡ n ) \mathcal O((n+q)\log n) O((n+q)logn)

#include 

using namespace std;

namespace SETS {

int fa[400005];

void init(int n) {
  for(int i=1;i<=n;i++) fa[i]=i;
}

int find_father(int x) {
  return (fa[x]==x)?x:fa[x]=find_father(fa[x]);
}

bool check(int x,int y) {
  x=find_father(x);y=find_father(y);
  return x!=y;
}

void merge(int x,int y) {
  x=find_father(x);y=find_father(y);
  if (x==y) return;
  fa[x]=y;
}

}

vector <int> e[400005];

int dep[400005],fa[400005][20];

void dfs(int x) {
  for(int i=0;i<e[x].size();i++)
    if (e[x][i]!=fa[x][0]) {
    	int u=e[x][i];
    	fa[u][0]=x;dep[u]=dep[x]+1;
    	for(int j=1;j<20;j++) fa[u][j]=fa[fa[u][j-1]][j-1];
    	dfs(u);
	}
}

int lca(int x,int y) {
  if (dep[x]<dep[y]) swap(x,y);
  int d=dep[x]-dep[y];
  for(int i=0;i<20;i++)
    if ((d>>i)&1) x=fa[x][i];
  if (x==y) return x;
  for(int i=19;i>=0;i--)
    if (fa[x][i]!=fa[y][i]) {
    	x=fa[x][i];
    	y=fa[y][i];
	}
  return fa[x][0];
}

int jump(int x,int d) {
  for(int i=0;i<20;i++)
    if ((d>>i)&1) x=fa[x][i];
  return x;
}

queue <int> q;
int num[200005],dis[400005];

void bfs(int k,int d) {
  memset(dis,0x3f,sizeof(dis));
  for(int i=1;i<=k;i++) {
  	dis[num[i]]=0;
  	q.push(num[i]);
  }
  while (!q.empty()) {
  	int x=q.front();q.pop();
  	if (dis[x]<d) {
  		for(int i=0;i<e[x].size();i++) {
  			int u=e[x][i];
  			SETS::merge(x,u);
  			if (dis[x]+1<dis[u]) {
  				dis[u]=dis[x]+1;
  				q.push(u);
			  }
		  }
	  }
  }
}

int main() {
  int n,d,k;
  scanf("%d%d%d",&n,&d,&k);
  SETS::init(2*n-1);
  for(int i=1;i<n;i++) {
  	int x,y;
  	scanf("%d%d",&x,&y);
  	e[x].push_back(n+i);
  	e[n+i].push_back(x);
  	e[y].push_back(n+i);
  	e[n+i].push_back(y);
  }
  dfs(1);
  for(int i=1;i<=k;i++) scanf("%d",&num[i]);
  bfs(k,d);
  int m;
  scanf("%d",&m);
  for(int i=1;i<=m;i++) {
  	int x,y;
  	scanf("%d%d",&x,&y);
  	int p=lca(x,y),len=dep[x]+dep[y]-2*dep[p];
  	if (len<=2*d) {
  		puts("YES");
  		continue;
	  }
	int u=((dep[x]-dep[p]>=d)?jump(x,d):jump(y,len-d));
	int v=((dep[y]-dep[p]>=d)?jump(y,d):jump(x,len-d));
	puts((SETS::check(u,v))?"NO":"YES");
  }
  return 0;
}

G. Cow and Exercise

似乎是个非常古老的原题,不知道怎么被出到cf上的。
x i = P x_i=P xi=P,考虑将问题描述为一个线性规划:
max ⁡ d n − d 1   d v i − d u i − x i ≤ w i ( 1 ≤ i ≤ m ) ∑ i = 1 m x i ≤ P d i ≥ 0 ( 1 ≤ i ≤ n ) \max \quad d_n-d_1\\ \begin{aligned} \ d_{v_i}-d_{u_i}-x_i &\leq w_i \quad (1\leq i\leq m) \\ \sum_{i=1}^m x_i &\leq P \\ d_i & \geq 0 \quad (1\leq i \leq n) \end{aligned} maxdnd1 dviduixii=1mxidiwi(1im)P0(1in)
直接做不好做,考虑它的对偶问题:
min ⁡ ( ∑ i = 1 m w i y i ) + P z ∑ u ∈ i n 1 y u − ∑ v ∈ o u t 1 y v ≥ − 1 ∑ u ∈ i n i y u − ∑ v ∈ o u t i y v ≥ 0 ( 1 < i < n ) ∑ u ∈ i n n y u − ∑ v ∈ o u t n y v ≥ 1 z − y i ≥ 0 ( 1 ≤ i ≤ m ) z ≥ 0 , y i ≥ 0 ( 1 ≤ i ≤ m ) \min \quad (\sum_{i=1}^{m}w_iy_i )+Pz \\ \begin {aligned} \sum_{u \in in_1}y_u - \sum_{v \in out_1} y_v &\geq -1 \\ \sum_{u \in in_i}y_u - \sum_{v \in out_i} y_v &\geq 0 \quad (1min(i=1mwiyi)+Pzuin1yuvout1yvuiniyuvoutiyvuinnyuvoutnyvzyiz0,yi10(1<i<n)10(1im)0(1im)
把前三类不等式左右分别相加,有 ∑ i = 1 n ( ∑ u ∈ i n i y u − ∑ v ∈ o u t i y v ) ≤ 0 \sum_{i=1}^{n} (\sum_{u \in in_i}y_u-\sum_{v \in out _i} y_v) \leq 0 i=1n(uiniyuvoutiyv)0,注意所有的 i n i in_i ini o u t i out_i outi会全部抵消掉,因此右边必须全取等号。
注意到显然有 z > 0 z>0 z>0,令 t i = z ⋅ y i t_i=z\cdot y_i ti=zyi,转化一下有:
min ⁡ ( ∑ i = 1 m w i t i + P ) / z ∑ u ∈ i n 1 t u − ∑ v ∈ o u t 1 t v = − z ∑ u ∈ i n i t u − ∑ v ∈ o u t i t v = 0 ( 1 < i < n ) ∑ u ∈ i n n t u − ∑ v ∈ o u t n t v = z z > 0 , 0 ≤ t i ≤ 1 ( 1 ≤ i ≤ m ) \min \quad (\sum_{i=1}^{m}w_it_i +P)/z \\ \begin {aligned} \sum_{u \in in_1}t_u - \sum_{v \in out_1} t_v &= -z \\ \sum_{u \in in_i}t_u - \sum_{v \in out_i} t_v &= 0 \quad (1 0,0\leq t_i&\leq 1 \quad (1\leq i\leq m) \end{aligned} min(i=1mwiti+P)/zuin1tuvout1tvuinituvoutitvuinntuvoutntvz>0,0ti=z=0(1<i<n)=z1(1im)
这看起来是一个最小费用流模型。具体的,令 f ( z ) f(z) f(z)表示源点为 1 1 1,汇点为 n n n,每条边 ( u i , v i ) (u_i,v_i) (ui,vi)流量上限为 1 1 1,单位费用为 w i w_i wi,且流量为 z z z的最小费用,那么答案即为 min ⁡ ( ( f ( z ) + P ) / z ) \min((f(z)+P)/z) min((f(z)+P)/z)。注意到根据费用流的性质, f ( z ) f(z) f(z)是一个下凸函数,因此有用的只有 ( z , f ( z ) ) (z,f(z)) (z,f(z))下凸壳上的 z z z,这个可以用最短增广路算法求得所有可能有用的 z z z。并且由于流量上限均为整数且流量不超过 n n n,因此至多增广 n n n次。构造出下凸壳后,每次给定 P P P查询直接在凸壳上三分即可。
时间复杂度为 O ( n 2 m + q log ⁡ n ) \mathcal O(n^2m+q\log n) O(n2m+qlogn)

#include 
#define inf 0x3f3f3f3f

using namespace std;

typedef long long ll;
typedef double db;

struct Edge {
  int t,f,v,next;
  Edge() {}
  Edge(int a,int b,int c,int d):t(a),f(b),v(c),next(d) {}
};

Edge e[1000005];
int head[55],vs,vt,tot=-1;

inline void addEdge(int x,int y,int z,int v) {
  e[++tot]=Edge(y,z,v,head[x]);
  head[x]=tot;
  e[++tot]=Edge(x,0,-v,head[y]);
  head[y]=tot;
}

struct Point {
  ll x,y;
  Point() {}
  Point(ll a,ll b):x(a),y(b) {}
  Point operator - (Point b) {return Point(x-b.x,y-b.y);}
};

inline ll cross(Point x,Point y) {
  return x.x*y.y-x.y*y.x;
}

inline db calc(Point x,ll k) {
  return (db)(k+x.y)/x.x;
}

Point p[55],st[55];
int sz;

void gethull() {
  int top=1;
  st[1]=Point(0,0);
  for(int i=1;i<=sz;i++) {
  	while (top>1&&cross(p[i]-st[top],st[top]-st[top-1])>=0) top--;
  	st[++top]=p[i];
  }
  sz=top-1;
  for(int i=1;i<=sz;i++) p[i]=st[i+1];
}

db query(ll k) {
  int l=1,r=sz;
  while (r-l>2) {
    int m1=l+(r-l)/3,m2=r-(r-l)/3;
    if (calc(p[m1],k)<=calc(p[m2],k)) r=m2; else l=m1;
  }
  db ans=1e18;
  for(int i=l;i<=r;i++) ans=min(ans,calc(p[i],k));
  return ans;
}

namespace Flow {

int dis[55],cur[55];
bool in[55];
queue <int> q;

bool spfa() {
  memset(in,0,sizeof(in));
  memset(dis,0x3f,sizeof(dis));
  dis[vs]=0;in[vs]=1;
  q.push(vs);
  while (!q.empty()) {
  	int x=q.front();q.pop();
	in[x]=0;cur[x]=head[x];
  	for(int i=head[x];i!=-1;i=e[i].next)
  	  if (e[i].f&&dis[x]+e[i].v<dis[e[i].t]) {
  	  	  int u=e[i].t;
  	  	  dis[u]=dis[x]+e[i].v;
  	  	  if (!in[u]) {
  	  	  	    in[u]=1;
  	  	  	    q.push(u);
			  }
		}
  }
  return dis[vt]<inf;
}

int vis[55],cnt;

int dfs(int x,int a) {
  if (x==vt||!a) return a;
  vis[x]=cnt;
  int ans=0;
  for(int &i=cur[x];i!=-1;i=e[i].next)
    if (e[i].f&&vis[e[i].t]<cnt&&dis[x]+e[i].v==dis[e[i].t]) {
    	int u=e[i].t;
    	int f=dfs(u,min(a,e[i].f));
    	if (f) {
    		e[i].f-=f;
    		e[i^1].f+=f;
    		ans+=f;
    		a-=f;
    		if (!a) break;
		}
	}
  return ans;
}

void msmf() {
  ll s1=0,s2=0;
  while (spfa()) {
  	int t;
  	do {
  		cnt++;
  		t=dfs(vs,inf);
  		s1+=t;
  		s2+=(ll)t*dis[vt];
	  } while (t);
	p[++sz]=Point(s1,s2);
  }
}

}

int main() {
  memset(head,255,sizeof(head));
  int n,m;
  scanf("%d%d",&n,&m);
  vs=1;vt=n;
  for(int i=1;i<=m;i++) {
  	int x,y,z;
  	scanf("%d%d%d",&x,&y,&z);
  	addEdge(x,y,1,z);
  }
  Flow::msmf();
  gethull();
  int k;
  scanf("%d",&k);
  for(int i=1;i<=k;i++) {
  	int x;
  	scanf("%d",&x);
  	printf("%.10f\n",query(x));
  }
  return 0;
}

你可能感兴趣的:(codeforces,图论,线性规划)