Codeforces Round 1396 简要题解

47 篇文章 1 订阅
23 篇文章 1 订阅

A. Multiples of Length

B. Stoned Game

C. Monster Invaders

问题可以转化为从level 1 1 1开始,每次花费 d d d的时间移动到一个相邻的level,然后对于level i i i,我们有两种选择:第一种是花费 p i = r 1 a i + r 3 p_i=r_1a_i+r_3 pi=r1ai+r3时间,需要至少经过level i i i一次;第二种是花费 q i = min ⁡ ( r 1 ( a i + 2 ) , r 1 + r 2 ) q_i=\min(r_1(a_i+2),r_1+r_2) qi=min(r1(ai+2),r1+r2)时间,需要至少经过level i i i两次,问最小花费时间。
考虑 f i f_i fi i < n i<n i<n)表示我们第一次到达level i i i,不返回level i − 1 i-1 i1以前,干掉level i i i到level n n n的所有怪的最小时间。那么显然有几种策略:第一种是只经过level i i i一次,花费 f i + 1 + d + p i f_{i+1}+d+p_i fi+1+d+pi时间;否则后面还会返回level i i i,那么我们考虑第一次返回level i i i前的最高level k k k,若 k < n k<n k<n显然我们下一次会直接从level i i i走到level k + 1 k+1 k+1,再也不会返回level k k k k = n − 1 k=n-1 k=n1可能要特殊讨论),这样花费 ( ∑ j = i k min ⁡ ( p j , q j ) ) + ( 3 ( k − i ) + 1 ) d + f k + 1 (\sum_{j=i}^{k}\min(p_j,q_j))+(3(k-i)+1)d+f_{k+1} (j=ikmin(pj,qj))+(3(ki)+1)d+fk+1时间;否则 k = n k=n k=n且返回level i i i,这样显然除level n n n都至少走了两次,简单讨论一下是否在level n − 1 n-1 n1和level n n n之间折返即可。
倒着做上述DP,容易实现到 O ( n ) \mathcal O(n) O(n)

#include <bits/stdc++.h>
#define FR first
#define SE second
#define y1 yy
#define last last2
#define inf 0x3f3f3f3f3f3f3f3f

using namespace std;

typedef pair<int,int> pr;
typedef long long ll;

ll val1[1000005],val2[1000005];
ll f[1000005],sum[1000005];

int main() {
  int n;
  ll a,b,c,d;
  scanf("%d%lld%lld%lld%lld",&n,&a,&b,&c,&d);
  for(int i=1;i<=n;i++) {
  	int x;
  	scanf("%d",&x);
  	val1[i]=a*x+c;
  	val2[i]=min(a+b,a*(x+2));
  }
  for(int i=1;i<=n;i++) sum[i]=sum[i-1]+min(val1[i],val2[i]);
  f[n]=val1[n];
  ll minn=inf;
  for(int i=n-1;i>0;i--) {
  	f[i]=min(f[i+1]+val1[i]+d,min(d*min(2*(n-i)+2,3*(n-i))+sum[n]-sum[i-1],d*(2*(n-i))+sum[n-1]-sum[i-1]+val1[n]));
  	if (i+2<=n) minn=min(minn,f[i+2]+d*(3*(i+2)-2)+sum[i+1]);
  	if (minn<inf) f[i]=min(f[i],minn-d*(3*i)-sum[i-1]);
  }
  printf("%lld\n",f[1]);
  return 0;
}

D. Rainbow Rectangles

首先将坐标离散化到 [ 1 , n ] [1,n] [1,n]之间。
枚举矩形的右边界 x 2 x_2 x2,对左边界 x 1 x_1 x1从小到大做扫描线。若 x 1 = 1 x_1=1 x1=1,我们容易用two pointer得到每个下边界 y 1 y_1 y1对应的最小上边界 y 2 y_2 y2(所有 ≥ Y 2 \geq Y_2 Y2的显然都可行)。每次增大 x 1 x_1 x1,意味着我们需要删除若干个点,若我们删除了某个 ( x i , y i , c i ) (x_i,y_i,c_i) (xi,yi,ci),那么我们考虑 c = c i c=c_i c=ci且还未被删除的点中,若还有 y = y i y=y_i y=yi的显然不会影响,否则考虑 y < y i y<y_i y<yi的最大的 y l y_l yl(不存在设为 0 0 0), y > y i y>y_i y>yi的最小的 y r y_r yr(不存在设为 n + 1 n+1 n+1),那么我们会将所有 y ∈ ( y l , y i ] y\in(y_l,y_i] y(yl,yi]的最小上边界与 y r y_r yr max ⁡ \max max。注意到任何时刻每个 y y y对应的最小上边界单调不降,那么可以用set维护连续段的trick,均摊 O ( log ⁡ n ) \mathcal O(\log n) O(logn)更新。
总时间复杂度为 O ( n 2 log ⁡ n ) \mathcal O(n^2\log n) O(n2logn)

#include <bits/stdc++.h>
#define FR first
#define SE second
#define y1 yy
#define last last2
#define inf 0x3f3f3f3f3f3f3f3f
#define MOD 1000000007
#define last last2

using namespace std;

typedef pair<int,int> pr;
typedef long long ll;

int xv[2005],yv[2005];

set <int> st1;
multiset <int> st2[2005]; 
int val[2005];

struct Point {
  int x,y,c;
  Point() {}
  Point(int a,int b,int c):x(a),y(b),c(c) {}
};

bool cmp1(Point x,Point y) {
  return x.x<y.x;
}

bool cmp2(Point x,Point y) {
  return x.y<y.y;
}

Point p[2005],q[2005];
int cnt[2005];

int main() {
  int n,m,L;
  scanf("%d%d%d",&n,&m,&L);
  for(int i=1;i<=n;i++) {
  	int x,y,z;
  	scanf("%d%d%d",&x,&y,&z);
  	x++;y++;
  	p[i]=Point(x,y,z);
  	xv[i]=x;
  	yv[i]=y;
  } 
  sort(xv+1,xv+n+1);
  sort(yv+1,yv+n+1);
  int sz1=unique(xv+1,xv+n+1)-xv-1,sz2=unique(yv+1,yv+n+1)-yv-1;
  xv[sz1+1]=yv[sz2+1]=L+1;
  for(int i=1;i<=n;i++) {
  	p[i].x=lower_bound(xv+1,xv+sz1+1,p[i].x)-xv;
  	p[i].y=lower_bound(yv+1,yv+sz2+1,p[i].y)-yv;
  	q[i]=p[i];
  }
  sort(p+1,p+n+1,cmp1);
  sort(q+1,q+n+1,cmp2);
  ll ans=0;
  for(int i=1;i<=sz1;i++) {
  	memset(cnt,0,sizeof(cnt));
  	ll s=0;
  	int r1=n,r2=n,csz=0;
  	for(int j=sz2;j>0;j--) {
  		while (r1&&q[r1].y>=j) {
  			if (q[r1].x<=i) {
  				if (!cnt[q[r1].c]) csz++;
  				cnt[q[r1].c]++;
			  }
			r1--;
		  }
		if (csz>=m) {
			while (r2&&q[r2].y>=j) {
				if (q[r2].x>i) r2--;
				else if (cnt[q[r2].c]>1) {
					cnt[q[r2].c]--;
					r2--;
				}
				else break;
			}
			val[j]=q[r2].y;
		}
		else val[j]=sz2+1;
		s=(s+(ll)(L+1-yv[val[j]])*(yv[j]-yv[j-1]))%MOD;
	  }
	st1.clear();
	for(int j=0;j<=m;j++) st2[j].clear();
	for(int j=1;j<=sz2;j++) 
	  if (j==sz2||val[j]!=val[j+1]) st1.insert(j);
	for(int j=1;j<=n;j++)
	  if (p[j].x<=i) st2[p[j].c].insert(p[j].y);
	r1=1;
	for(int j=1;j<=i;j++) {
		ans=(ans+s*(xv[j]-xv[j-1])%MOD*(xv[i+1]-xv[i]))%MOD;
		while (r1<=n&&p[r1].x<=j) {
			multiset<int>::iterator it=st2[p[r1].c].lower_bound(p[r1].y),it2=it;
			it2++;
			if (it2!=st2[p[r1].c].end()&&(*it2)==(*it)) {
				st2[p[r1].c].erase(it);
				r1++;
				continue;
			} 
			it2=it;
			int l;
			if (it2==st2[p[r1].c].begin()) l=1;
			else {
				it2--;
				l=(*it2)+1;
			}
			int r;
			it2=it;
			it2++;
			if (it2==st2[p[r1].c].end()) r=sz2+1;
			else r=(*it2);
			st2[p[r1].c].erase(it);
			set<int>::iterator it3=st1.lower_bound(l),it4=it3;
			if (val[*it3]>=r) {
				r1++;
				continue;
			}
			if (it4!=st1.begin()) {
				it4--;
				if ((*it4)<l-1) {
					val[l-1]=val[*it3];
					st1.insert(l-1);
				}
			}
			else if (l>1) {
				val[l-1]=val[*it3];
				st1.insert(l-1);
			}
			int last=l-1;
			for(;it3!=st1.end()&&val[*it3]<r;it3=st1.lower_bound(l)) {
				int cur=(*it3);
				s=(s-(ll)(yv[r]-yv[val[cur]])*(yv[cur]-yv[last])%MOD+MOD)%MOD;
				st1.erase(it3);
				last=cur;
			}
			val[last]=r;
			st1.insert(last);
			r1++;
		}
	}
  }
  printf("%lld\n",ans);
  return 0;
}

E. Distance Matching

首先将无根树转化为以重心 c c c为根。
对于某个匹配 i − j i-j ij,贡献为 d e p i + d e p j − 2 ⋅ d e p l c a ( i , j ) dep_i+dep_j-2\cdot dep_{lca(i,j)} depi+depj2deplca(i,j)。那么可以把最后答案转化为只跟每组匹配的lca深度和有关。容易用贪心思想得到最小的距离和 k l k_l kl,最大的距离和 k r k_r kr(事实上此时所有的lca = c =c =c)。显然 k l k_l kl k r k_r kr奇偶性相同,那么若 k ∉ [ k l , k r ] k\notin[k_l,k_r] k/[kl,kr]或奇偶性不相同显然无解,否则下面的构造可以给出一组解。
注意到我们对达到 k l k_l kl的方案中,每将一个lca改为父亲的话可以把距离和增加 2 2 2。我们尝试不断将某个lca换成它父亲,直到距离和达到 k k k为止。我们可以发现若我们找到某个子树中深度最浅的lca,将它不断上移的话,矛盾只可能出现在将它移到 c c c那里。由于 c c c是重心,我们如果每次选择一个里面还有lca,且 s i z e − 2 ⋅ size-2\cdot size2剩余 l c a lca lca个数最小的子树,将它里面某个lca往上移动一定不会出现矛盾。这样先搞出每次移动的子树顺序,再移动深度最小的lca,就可以构造出一组合法的lca方案了。
现在得到lca后还要还原匹配,这是容易做到的。我们可以dfs一遍,每次匹配完某个点子树内的,再匹配lca是它的。这是一个经典问题,每次在两个剩余点数最大的子树内各取一个点匹配即可。
这里需要优先队列,时间复杂度为 O ( n log ⁡ n ) \mathcal O(n\log n) O(nlogn)

#include <bits/stdc++.h>
#define FR first
#define SE second
#define last last2

using namespace std;

typedef long long ll;
typedef pair<int,int> pr;

vector<int> calc1(vector<int> vt) {
  priority_queue<pr,vector<pr>,greater<pr>> q;
  for(int i=0;i<vt.size();i++)
    if (vt[i]>1) q.push(pr((vt[i]&1),i));
  vector<int> que;
  while (!q.empty()) {
  	pr t=q.top();q.pop();
  	que.push_back(t.SE);
  	if (t.FR+2<vt[t.SE]) q.push(pr(t.FR+2,t.SE));
  }
  return que;
}

vector<pr> calc2(int n,vector<int> vt) {
  priority_queue<pr> q;
  for(int i=0;i<vt.size();i++) q.push(pr(vt[i],i));
  int last=0;
  vector<pr> que;
  for(int i=1;i<=n;i++) {
  	pr t1=q.top();q.pop();
  	pr t2=q.top();q.pop();
  	que.push_back(pr(t1.SE,t2.SE));
	if (t1.FR>1) q.push(pr(t1.FR-1,t1.SE));
	if (t2.FR>1) q.push(pr(t2.FR-1,t2.SE)); 
  }
  return que;
}

vector <int> e[100005];
int siz1[100005],siz2[100005],n;
int fa[100005],dep[100005];

void dfs1(int x) {
  siz1[x]=1;
  for(int i=0;i<e[x].size();i++)
    if (e[x][i]!=fa[x]) {
    	int u=e[x][i];
    	fa[u]=x;dep[u]=dep[x]+1;
    	dfs1(u);
    	siz1[x]+=siz1[u];
    	siz2[x]=max(siz2[x],siz1[u]);
	}
  siz2[x]=max(siz2[x],n-siz1[x]);
}

int dfn[100005],ed[100005],num[100005],dfs_cnt;
int val[100005];

void dfs2(int x) {
  dfn[x]=++dfs_cnt;
  num[dfs_cnt]=x;
  for(int i=0;i<e[x].size();i++)
    if (e[x][i]!=fa[x]) dfs2(e[x][i]);
  ed[x]=dfs_cnt;
}

priority_queue <pr,vector<pr>,greater<pr> > q1[100005];
queue <int> q2[100005];

void dfs3(int x) {
  static int pos[100005];
  for(int i=0;i<e[x].size();i++)
    if (e[x][i]!=fa[x]) dfs3(e[x][i]);
  vector<int> vt1;
  int sz=0;
  for(int i=0;i<e[x].size();i++)
    if (e[x][i]!=fa[x]&&q2[e[x][i]].size()) {
    	int u=e[x][i];
    	pos[sz++]=u;
    	vt1.push_back(q2[u].size());
	}
  pos[sz++]=x;
  vt1.push_back(1);
  q2[x].push(x);
  vector<pr> vt2=calc2(val[x],vt1);
  for(int i=0;i<vt2.size();i++) {
    int u=pos[vt2[i].FR],v=pos[vt2[i].SE];
    printf("%d %d\n",q2[u].front(),q2[v].front());
    q2[u].pop();
    q2[v].pop();
  }
  for(int i=0;i<e[x].size();i++)
    if (e[x][i]!=fa[x]) {
    	int u=e[x][i];
    	if (q2[u].size()>q2[x].size()) swap(q2[x],q2[u]);
    	while (!q2[u].empty()) {
    		q2[x].push(q2[u].front());
    		q2[u].pop();
		}
	}
}

int main() {
  ll k;
  scanf("%d%lld",&n,&k);
  for(int i=1;i<n;i++) {
  	int x,y;
  	scanf("%d%d",&x,&y);
  	e[x].push_back(y);
  	e[y].push_back(x);
  }
  dfs1(1);
  int rt=1;
  for(int i=1;i<=n;i++)
    if (siz2[i]<siz2[rt]) rt=i;
  fa[rt]=dep[rt]=0;
  dfs1(rt);
  for(int i=1;i<=n;i++) {
  	val[i]=siz1[i];
  	for(int j=0;j<e[i].size();j++)
  	  if (fa[e[i][j]]==i) val[i]-=(siz1[e[i][j]]>>1<<1);
  	val[i]>>=1;
  }
  ll s1=0,s2=0;
  for(int i=1;i<=n;i++) {
  	s1+=dep[i];
  	if (siz1[i]<n) s2+=siz1[i];
  	s1-=2LL*val[i]*dep[i];
  }
  if (((k^s1)&1)||k<s1||k>s2) {
  	puts("NO");
  	return 0;
  }
  puts("YES");
  dfs2(rt);
  vector<int> vt1;
  for(int i=0;i<e[rt].size();i++) {
  	int x=e[rt][i],s=0;
  	for(int j=dfn[x];j<=ed[x];j++) {
  		int u=num[j];
  		for(int k=1;k<=val[u];k++) {
  			s++;
  			q1[i].push(pr(dep[u],u));
		  }
	  }
	vt1.push_back(siz1[x]);
  }
  vt1.push_back(1);
  vector<int> vt2=calc1(vt1);
  for(int i=0;i<vt2.size();i++) {
  	int x=vt2[i];
  	pr t=q1[x].top();q1[x].pop();
  	if (s1+2LL*t.FR<k) {
  		s1+=2LL*t.FR;
  		val[t.SE]--;
  		val[rt]++;
	  }
	else {
		int u=t.SE;
		for(int j=1;j<=((k-s1)>>1);j++) u=fa[u];
		val[t.SE]--;
		val[u]++;
		break;
	}
  }
  dfs3(rt);
  return 0;
} 
/*
6 5
1 2
3 2
2 4
4 5
4 6 
*/ 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值