Codeforces Round 1344 简要题解

27 篇文章 1 订阅
20 篇文章 0 订阅

A. Hilbert’s Hotel

B. Monopole Magnets

C. Quantifier Question

D. Résumé Review

f ( i , x ) f(i,x) f(i,x) 0 ≤ x < a i 0\leq x<a_i 0x<ai)表示 b i b_i bi x x x变为 x + 1 x+1 x+1答案的增量,那么有 f i ( x ) = ( x + 1 ) ( a i − ( x + 1 ) 2 ) − x ( a i − x 2 ) = − 3 x 2 − 4 x + ( a − 1 ) f_i(x)=(x+1)(a_i-(x+1)^2)-x(a_i-x^2)=-3x^2-4x+(a-1) fi(x)=(x+1)(ai(x+1)2)x(aix2)=3x24x+(a1),在定义域内单调递减。
那么有一个显然的贪心是一开始让所有的 b i = 0 b_i=0 bi=0,每次选一个 b i < a i b_i<a_i bi<ai f ( i , b i ) f(i,b_i) f(i,bi)最大的 b i b_i bi增加 1 1 1,重复 k k k次。
分析一下,这个贪心算法其实是选择了前 k k k大的 f ( i , x ) f(i,x) f(i,x)。那么显然可以考虑二分一下第 k k k大的 f ( i , x ) f(i,x) f(i,x),假设我们二分了一个 m i d mid mid,只需要对每个 i i i计算有多少 f ( i , x ) ≥ m i d f(i,x)\geq mid f(i,x)mid,这个可以再二分或者直接解一个二次不等式。
复杂度取决于内层计算 f ( i , x ) ≥ m i d f(i,x)\geq mid f(i,x)mid的解数的方式,这份代码选择了直接二分,时间复杂度为 O ( n log ⁡ 2 V ) \mathcal O(n\log^2V) O(nlog2V),也可以优化到 O ( n log ⁡ V ) \mathcal O(n\log V) O(nlogV)

#include <bits/stdc++.h>
#define FR first
#define SE second
#define inf 0x7f7f7f7f

using namespace std;

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

inline ll F(ll x,ll y) {
  return x-1-3LL*y*y-4LL*y;
}

ll calc(ll x,ll d) {
  if (x-1<d) return 0;
  ll l=0,r=x-1;
  while (l<r) {
  	ll mid=((l+r)>>1)+1;
  	if (F(x,mid)>=d) l=mid; else r=mid-1;
  }
  return l+1;
} 

ll num[100005];

ll check(int n,ll d) {
  ll s=0;
  for(int i=1;i<=n;i++) s+=calc(num[i],d);
  return s;
}

ll ans[100005];

ll query(ll x,ll y) {
  return y*(x-y*y);
}

int main() {
  int n;
  ll k;
  scanf("%d%lld",&n,&k);
  ll l=inf,r=-inf;
  for(int i=1;i<=n;i++) {
    scanf("%lld",&num[i]);
    l=min(l,F(num[i],num[i]-1));
    r=max(r,F(num[i],0));
  }
  while (l<r) {
  	ll mid=((l+r)>>1);
  	if (check(n,mid)<=k) r=mid; else l=mid+1;
  }
  for(int i=1;i<=n;i++) {
  	ll t=calc(num[i],l);
  	k-=t;
  	ans[i]=t;
  }
  for(int i=1;i<=n;i++)
    if (k&&ans[i]<num[i]&&F(num[i],ans[i])==l-1) {
    	ans[i]++;
    	k--;
	}
  for(int i=1;i<=n;i++) printf("%lld ",ans[i]);
  printf("\n");
  return 0;
}

E. Train Tracks

考虑对于每个非叶子站点 x x x,所有会经过它(不停在 x x x)的火车按经过 x x x的时间 t i t_i ti排序可以得到若干个 ( t i , e i ) (t_i,e_i) (ti,ei),其中 e i e_i ei是接下来要走的边,且有 t 0 = 0 t_0=0 t0=0 e 0 e_0 e0为初始的边。显然,对于一对相邻的 ( t i − 1 , e i − 1 ) (t_{i-1},e_{i-1}) (ti1,ei1) ( t i , e i ) (t_i,e_i) (ti,ei),若 e i − 1 ≠ e i e_{i-1}\neq e_i ei1=ei,我们需要在 [ t i − 1 + 1 , t i ] [t_{i-1}+1,t_i] [ti1+1,ti]的时间内对 x x x进行一次操作,否则会在时刻 t i t_i ti爆炸。
如果我们把所有点对应的所有这些需要操作的时间区间 [ L i , R i ] [L_i,R_i] [Li,Ri]都拿出来,并且按 L i L_i Li排序,那么就可以做一个经典的贪心:依次扫描每个有用的时刻,用一个堆维护当前可以操作且还没有操作的区间对应的 R R R,每次取出最小的 R R R操作。如果某个时刻堆顶的 R R R小于当前时刻,第一问的答案即为这个 R R R,否则第一问的答案为 i n f inf inf − 1 -1 1)。第二问的答案显然是小于第一问答案的 R i R_i Ri数目。
现在问题在于怎么找出所有这种时间区间 [ L i , R i ] [L_i,R_i] [Li,Ri],这是一个经典的数据结构问题:考虑从底往上启发式合并所有的火车的时间,用一个set维护顺序,因为我们只需要考虑 e i e_i ei不同的,每次合并的时候只需要暴力枚举较小的set,查找它里面元素在合并后set的前驱后继即可,注意还要处理最开始的火车。这部分总共会找出 O ( n + m log ⁡ n ) \mathcal O(n+m\log n) O(n+mlogn)个时间区间。
时间复杂度为 O ( n log ⁡ n + m log ⁡ 2 n ) \mathcal O(n\log n+m\log^2n) O(nlogn+mlog2n)

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

using namespace std;

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

vector <pr> e[100005];
ll dis[100005];
int dfn[100005],ed[100005],dfs_cnt;

void dfs1(int x) {
  dfn[x]=++dfs_cnt;
  for(int i=0;i<e[x].size();i++) {
  	int u=e[x][i].FR;
  	dis[u]=dis[x]+e[x][i].SE;
  	dfs1(u);
  }
  ed[x]=dfs_cnt;
}

pr num[10000005];
int cnt;

set <pr> q[100005];
vector <int> vt[100005];

inline bool issub(int x,int y) {
  return dfn[x]<=dfn[y]&&ed[x]>=dfn[y];
}

void dfs2(int x) {
  if (!e[x].size()) {
  	for(int i=0;i<vt[x].size();i++) q[x].insert(pr(vt[x][i],x));
  	return;
  }
  int son=e[x][0].FR;
  for(int i=0;i<e[x].size();i++)  {
  	int u=e[x][i].FR;
  	dfs2(u);
  	if (q[u].size()>q[son].size()) son=u;
  }
  swap(q[x],q[son]);
  for(int i=0;i<e[x].size();i++)
    if (e[x][i].FR!=son) {
    	int u=e[x][i].FR;
    	for(set<pr>::iterator it=q[u].begin();it!=q[u].end();it++)
    	  q[x].insert(*it);
	}
  for(int i=0;i<e[x].size();i++)
    if (e[x][i].FR!=son) {
    	int u=e[x][i].FR;
    	for(set<pr>::iterator it=q[u].begin();it!=q[u].end();it++) {
    		set<pr>::iterator it2=q[x].find(*it),it3=it2;
    		if (it2!=q[x].begin()) {
    			it2--;
    			if (!issub(u,it2->SE)) num[++cnt]=pr((it2->FR)+1+dis[x],(it->FR)+dis[x]);
			}
			it3++;
			if (it3!=q[x].end()) {
				if (issub(son,it3->SE)) num[++cnt]=pr((it->FR)+1+dis[x],(it3->FR)+dis[x]);
			}
		}
	}
  int last=e[x][e[x].size()-1].FR;
  if (q[x].size()&&!issub(last,(q[x].begin())->SE)) num[++cnt]=pr(1,((q[x].begin())->FR)+dis[x]);
  for(int i=0;i<vt[x].size();i++) q[x].insert(pr(vt[x][i],x));
}

priority_queue <ll,vector<ll>,greater<ll> > que;

ll solve() {
  sort(num+1,num+cnt+1);
  num[cnt+1]=pr(inf,0);
  for(int i=1,j=1;i<=cnt;i=j+1) {
  	while (j<cnt&&num[j+1].FR==num[i].FR) j++;
  	for(int k=i;k<=j;k++) que.push(num[k].SE);
  	ll d=num[i].FR;
  	while (!que.empty()&&d<num[j+1].FR) {
  		ll x=que.top();que.pop();
  		if (x<d) return x;
  		d++;
	  }
  }
  return inf;
}

int main() {
  int n,m;
  scanf("%d%d",&n,&m);
  for(int i=1;i<n;i++) {
  	int x,y,z;
  	scanf("%d%d%d",&x,&y,&z);
  	e[x].push_back(pr(y,z));
  }
  dfs1(1);
  for(int i=1;i<=m;i++) {
  	int x,y;
  	scanf("%d%d",&x,&y);
  	vt[x].push_back(y);
  }
  dfs2(1);
  ll ans1=solve();
  int ans2=0;
  for(int i=1;i<=cnt;i++)
    if (num[i].SE<ans1) ans2++;
  printf("%lld %d\n",(ans1<inf)?ans1:-1,ans2);
  return 0;
}

F. Piet’s Palette

按套路,考虑找出mix操作中的不变量。构造一下,令 ′ W ′ , ′ R ′ , ′ Y ′ , ′ B ′ 'W','R','Y','B' W,R,Y,B的权值分别为 0 , 1 , 2 , 3 0,1,2,3 0,1,2,3,那么每次操作后异或和不变。从这个角度也可以发现操作顺序是不会影响答案的,最后结果即为所有元素的异或和。
注意到异或和可以拆位,给每个位置 i i i定义两个变量 x i x_i xi y i y_i yi 01 01 01变量),分别表示两个位的权值,也即每个mix操作相当于给定了若干个 x i x_i xi的和与若干个 y i y_i yi的和在 F 2 \mathbb F_2 F2下的方程。
考虑修改操作,RY相当于交换 x i x_i xi y i y_i yi R B RB RB相当于让 y i y_i yi变为 ( x i + y i )   m o d     2 (x_i+y_i)\bmod \ 2 (xi+yi)mod 2 Y B YB YB相当于让 x i x_i xi变为 ( x i + y i )   m o d     2 (x_i+y_i) \bmod \ 2 (xi+yi)mod 2,那么任意时刻的 x i x_i xi y i y_i yi都可以由初始的 x i x_i xi y i y_i yi表示。
最后我们得到了 2 n 2n 2n个变量的 O ( k ) \mathcal O(k) O(k)个在 F 2 \mathbb F_2 F2下的方程,可以直接高斯消元,用bitset加速即可。
时间复杂度为 O ( n k + n 2 k w ) \mathcal O(nk+\frac{n^2k}{w}) O(nk+wn2k)

#include <bits/stdc++.h>

using namespace std;

typedef bitset<2005> bs;

bs a[2005];
int pos[2005];
int ans[2005];

bool gause(int n,int m) {
  int d=1;
  for(int i=1;i<=n;i++) {
  	if (!a[d][i]) {
  		for(int j=d+1;j<=m;j++)
  		  if (a[j][i]) {
  		  	  swap(a[d],a[j]);
  		  	  break;
			}
	  }
	if (!a[d][i]) continue;
	pos[d]=i;
	for(int j=1;j<=m;j++)
	  if (j!=d&&a[j][i]) a[j]^=a[d];
	d++;
  }
  for(int i=d;i<=m;i++)
    if (a[i][n+1]) return 0; 
  for(int i=1;i<d;i++) ans[pos[i]]=a[i][n+1];
  return 1;
}

bool num[1005][4];
int id[128],cur[1005];

const char *val=".RYB";

int main() {
  id['W']=0;id['R']=1;id['Y']=2;id['B']=3;
  int n,m;
  scanf("%d%d",&n,&m);
  int sz=0;
  for(int i=1;i<=n;i++) num[i][0]=num[i][3]=1;
  for(int i=1;i<=m;i++) {
  	char str[5];
  	int k;
  	scanf("%s%d",str,&k);
  	for(int j=1;j<=k;j++) scanf("%d",&cur[j]);
  	if (str[0]=='m') {
  		scanf("%s",str);
  		int v=id[str[0]];
  		++sz;
  		for(int j=1;j<=k;j++) {
  			int x=cur[j];
  			if (num[x][0]) a[sz].flip(2*x-1);
  			if (num[x][1]) a[sz].flip(2*x);
		  }
		if (v&1) a[sz].flip(2*n+1);
  		++sz;
  		for(int j=1;j<=k;j++) {
  			int x=cur[j];
  			if (num[x][2]) a[sz].flip(2*x-1);
  			if (num[x][3]) a[sz].flip(2*x);
		  }
		if ((v>>1)&1) a[sz].flip(2*n+1);
	  }
	else if (str[0]=='R'&&str[1]=='Y') {
		for(int j=1;j<=k;j++) {
			int x=cur[j];
			swap(num[x][0],num[x][2]);
			swap(num[x][1],num[x][3]);
		}
	}
	else if (str[0]=='R'&&str[1]=='B') {
		for(int j=1;j<=k;j++) {
			int x=cur[j];
			num[x][2]^=num[x][0];
			num[x][3]^=num[x][1];
		}
	}
	else {
		for(int j=1;j<=k;j++) {
			int x=cur[j];
			num[x][0]^=num[x][2];
			num[x][1]^=num[x][3];
		}
	}
  }
  if (!gause(2*n,sz)) {
  	puts("NO");
  	return 0;
  }
  puts("YES");
  for(int i=1;i<=n;i++) {
  	int s=(ans[2*i-1]^(ans[2*i]<<1));
  	putchar(val[s]);
  }
  printf("\n");
  return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值