Codeforces Round 1322简要题解

做手速狗的一场,E差5min调出来,不过还是苟到了rk4。

A. Unusual Competitions

B. Present

C. Instant Noodles

C ( S ) = ∑ i ∈ S c i C(S)=\sum_{i \in S}c_i C(S)=iSci,即 f ( S ) = C ( N ( S ) ) f(S)=C(N(S)) f(S)=C(N(S))
根据容斥原理,我们知道 f ( S ) = C ( N ( S ) ) = ∑ T ⊆ S ( − 1 ) ∣ T ∣ − 1 ⋅ C ( N ( T 1 ) ∩ N ( T 2 ) ∩ . . . ∩ N ( T ∣ T ∣ ) ) f(S)=C(N(S))=\sum_{T \subseteq S}(-1)^{|T|-1}\cdot C(N(T_1) \cap N(T_2) \cap ... \cap N(T_{|T|})) f(S)=C(N(S))=TS(1)T1C(N(T1)N(T2)...N(TT))。容易按 ∣ T ∣ |T| T从小到大归纳证明 ∀ S , k ∣ f ( S ) \forall S,k|f(S) S,kf(S)当且仅当 ∀ T , k ∣ C ( N ( T 1 ) ∩ N ( T 2 ) ∩ . . . ∩ N ( T ∣ T ∣ ) ) \forall T,k|C(N(T_1) \cap N(T_2) \cap ... \cap N(T_{|T|})) T,kC(N(T1)N(T2)...N(TT))
进一步的,令 N ′ ( i ) N'(i) N(i)为右部图中点 i i i在左部图中相邻的点的集合,可以证明答案即为 ∀ S , gcd ⁡ ( ∑ N ′ ( i ) = S c i ) \forall S,\gcd (\sum_{N'(i)=S}c_i) S,gcd(N(i)=Sci),于是问题变为将右部图中的点按 N ′ N' N分类。
这里可以用哈希实现,单组数据时间复杂度 O ( n + m ) \mathcal O(n+m) O(n+m)。或者直接将每个点的邻边用vector存起来,然后弄个 m a p < v e c t o r , l o n g l o n g > map map<vector,longlong>,可以证明这样单组数据是 O ( ( n + m ) log ⁡ n ) ) 的 \mathcal O((n+m)\log n))的 O((n+m)logn))

#include 
#define MOD1 170215161
#define MOD2 151728211

using namespace std;

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

map <pr,ll> mp;
ll num[500005];

int val1[500005],val2[500005];

int randint1() {
  return ((ll)rand()*89543534534LL+rand())%MOD1;
}

int randint2() {
  return ((ll)rand()*89543534534LL+rand())%MOD2;
}

int sum1[500005],sum2[500005];

int main() {
  srand(chrono::steady_clock::now().time_since_epoch().count());
  int cases;
  scanf("%d",&cases);
  for(;cases;cases--) {
  	mp.clear();
  	int n,m;
  	scanf("%d%d",&n,&m);
  	for(int i=1;i<=n;i++) scanf("%lld",&num[i]);
  	for(int i=1;i<=n;i++) {
  		val1[i]=randint1();
  		val2[i]=randint2();
  		sum1[i]=sum2[i]=0;
	  }
	for(int i=1;i<=m;i++) {
		int x,y;
		scanf("%d%d",&x,&y);
		sum1[y]=(sum1[y]+val1[x])%MOD1;
		sum2[y]=(sum2[y]+val2[x])%MOD2;
	}
	for(int i=1;i<=n;i++)
	  if (sum1[i]&&sum2[i]) mp[pr(sum1[i],sum2[i])]+=num[i];
	ll ans=0;
	for(map<pr,ll>::iterator it=mp.begin();it!=mp.end();it++)
	  ans=__gcd(ans,it->second);
	printf("%lld\n",ans);
  }
  return 0;
}

D. Reality Show

令第 i i i个人的权值是 2 l i 2^{l_i} 2li,那么贡献过程可以理解为二进制加法,每次进位时有贡献,于是顺序是不会影响结果的。
那么可以考虑从后往前dp,设 F [ i ] [ j ] [ k ] F[i][j][k] F[i][j][k]表示从后往前考虑到第 i i i个人,此前取的最大的 l i l_i li j j j,且对后面选择的 2 l i 2^{l_i} 2li做完二进制加法(只做到第 l i l_i li位)后,该位剩余 k k k的最小贡献和,转移考虑第 i i i个人是否选择即可。这个dp看起来是三次方的,但是注意到后两维相当于模拟了一个所有数的二进制加法,因此对于一个 i i i,有效的状态是 O ( n + m ) \mathcal O(n+m) O(n+m)的。
时间复杂度为 O ( n ( n + m ) ) \mathcal O(n(n+m)) O(n(n+m))

#include 
#define inf 0x3f3f3f3f3f3f3f3fLL

using namespace std;

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

vector<ll> f[2][2005];
int c[4005],w[2005],num[2005];

ll g[2005],val[4005][2005];
int vis[4005][2005],up[2005][2005],cnt;

void dfs(int x,int y,int t) {
  if (vis[x][y]==cnt) return;
  vis[x][y]=cnt;
  if (x==t||!y) {
  	val[x][y]=0;
  	up[x][y]=y;
  	return;
  }
  dfs(x+1,y>>1,t);
  val[x][y]=val[x+1][y>>1]+(ll)y*c[x];
  up[x][y]=up[x+1][y>>1];
}

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<=n;i++) scanf("%d",&w[i]);
  for(int i=1;i<=n+m;i++) scanf("%d",&c[i]);
  int cur=0;
  for(int i=n;i>0;i--) {
  	cur^=1;
  	for(int j=1;j<=m;j++) f[cur][j]=f[cur^1][j];
  	int maxd=1;
  	for(int j=0;j<=n;j++) g[j]=-inf;
  	g[1]=-w[i];
  	cnt++;
  	for(int j=1;j<=num[i];j++)
  	  for(int k=1;k<f[cur^1][j].size();k++) 
		if (f[cur^1][j][k]>-inf) {
  	  	  dfs(j,k,num[i]);
  	  	  int t=up[j][k]+1;
  	  	  maxd=max(maxd,t);
  	  	  g[t]=max(g[t],f[cur^1][j][k]+val[j][k]-w[i]);
		}
	maxd=max(maxd,(int)f[cur][num[i]].size()-1);
	for(int j=1;j<f[cur][num[i]].size();j++) g[j]=max(g[j],f[cur][num[i]][j]);
	f[cur][num[i]].resize(maxd+1);
	for(int j=0;j<f[cur][num[i]].size();j++) f[cur][num[i]][j]=g[j];
  }
  cnt++;
  ll ans=0;
  for(int i=1;i<=m;i++)
    for(int j=1;j<f[cur][i].size();j++)
      if (f[cur][i][j]>-inf) {
      	dfs(i,j,n+m+1);
      	ans=max(ans,f[cur][i][j]+val[i][j]);
	  }
  printf("%lld\n",ans);
  return 0;
}

E. Median Mountain Range

考虑枚举一个 k k k,将 < k <k的数字变为 0 0 0 ≥ k \geq k k的数字变为 1 1 1,得到一个 01 01 01序列,则最终的 a i a_i ai k k k的大小关系不变。
那么考虑 01 01 01序列 a ′ a' a,令 a 0 ′ = a 1 ′ a'_0=a'_1 a0=a1 a n + 1 ′ = a n ′ a'_{n+1}=a'_n an+1=an,我们发现对于一段连续的长度 > 1 >1 >1 0 0 0或者 1 1 1,里面的 a ′ a' a都不会改变,称这样的一个连续段为“柱子”。则柱子间形成若干个 01 01 01交替的连续段,可以发现每轮操作会将这样的连续段从左右缩进去一个单位(最左最右的 a ′ a' a与旁边的柱子合并了),这样容易得到最终的序列。
现在考虑扫描线,按权值从小到大考虑所有的 a i a_i ai,每次将初始的 a ′ a' a中一个 0 0 0变为 1 1 1,这样最终的 a ′ a' a也会将若干个 0 0 0变为 1 1 1。注意到此时最终的 a ′ a' a的改变是在修改前最终的 a ′ a' a的基础上,将至多一个区间中的 0 0 0全部变为 1 1 1(这些位置最终的答案即为 a i a_i ai)。用set和并查集维护初始的 a ′ a' a中所有的柱子,即可快速知道最终修改的区间,进而维护最终的 a ′ a' a,更新最终的 a a a以及答案。
时间复杂度为 O ( n log ⁡ n ) \mathcal O(n\log n) O(nlogn)

#include 
#define inf 0x3f3f3f3f3f3f3f3fLL

using namespace std;

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

struct SETS {

int fa[500005];

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]);
}

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

} st1,st2;

set <int> bel,equ;
int num[500005],ans[500005],maxn,n;
int fir[500005],id[500005];

void change1(int x,bool v) {
  if (x==1||num[x]==num[x-1]) equ.erase(x-1);
  if (x==n||num[x]==num[x+1]) equ.erase(x);
  num[x]=1;
  if (x==1||num[x]==num[x-1]) equ.insert(x-1);
  if (x==n||num[x]==num[x+1]) equ.insert(x);
  if (x==1) num[x-1]=1;
  if (x==n) num[x+1]=1;
  if (x>1&&num[x-1]) {
    st1.merge(x,x-1);
    st2.merge(x-1,x);
  }
  if (x<n&&num[x+1]) {
    st2.merge(x,x+1);
    st1.merge(x+1,x);
  }
}

void change2(int x,bool v) {
  int l=st1.find_father(x),r=st2.find_father(x);
  if (l<r||x==1||x==n) {
  	set<int>::iterator it=equ.lower_bound(l);
  	if (l>1&&it!=equ.begin()) {
  		it--;
  		int t=(*it)+1;
  		if (num[t]) {
		  if (v) maxn=max(maxn,(l-t)>>1);
		  l=t+1;
		} 
		else {
		  if (v) maxn=max(maxn,(l-t)>>1);
		  l=((t+l)>>1)+1; 
	    }
	  }
	it=equ.upper_bound(r);
	if (r<n&&it!=equ.end()) {
		int t=(*it);
		if (num[t]) {
		  if (v) maxn=max(maxn,(t-r)>>1);
		  r=t-1;
		} 
		else {
		  if (v) maxn=max(maxn,(t-r)>>1);
		  r=((t+r)>>1);
	    }
	}
  }
  else {
  	set<int>::iterator it1=equ.lower_bound(x);
  	it1--;
  	set<int>::iterator it2=equ.upper_bound(x);
  	int nl=(*it1)+1,nr=(*it2);
  	if (v) maxn=max(maxn,(nr-nl)>>1);
  	if (num[nl]&&num[nr]) {
  		l=nl+1;
		r=nr-1;
	  }
	else if (num[nl]) {
		l=nl+1;
		r=((nl+nr)>>1);
	}
	else if (num[nr]) {
		l=((nl+nr)>>1)+1;
		r=nr-1;
	}
	else {
		l=1;r=0;
	}
  }
  if (l<=r) {
  	for(;;) {
  		set<int>::iterator it=bel.lower_bound(l);
  		if (it!=bel.end()&&(*it)<=r) {
  			ans[*it]=fir[x];
  			bel.erase(it);
		  }
		else break;
	  }
  }
}

bool cmp(int x,int y) {
  return fir[x]<fir[y];
}

int main() {
  scanf("%d",&n);
  for(int i=1;i<=n;i++) {
    scanf("%d",&fir[i]);
    id[i]=i;
    bel.insert(i);
  }
  sort(id+1,id+n+1,cmp);
  st1.init(n);st2.init(n);
  for(int i=0;i<=n;i++) equ.insert(i);
  int r;
  for(int i=1;i<=n;i=r+1)
  	if (fir[id[i]]>fir[id[i-1]]) {
  		r=i;
  		while (r<n&&fir[id[r+1]]==fir[id[r]]) r++;
  		for(int j=i;j<=r;j++) 
		  change1(id[j],1);
  		for(int j=i;j<=r;j++) 
		  change2(id[j],1);
	  }
  printf("%d\n",maxn);
  for(int i=1;i<=n;i++) printf("%d ",ans[i]);
  printf("\n");
  return 0;
}

F. Assigning Fares

忽略没有被路径覆盖的边,我们可以给第 i i i条边设一个 01 01 01变量 p i p_i pi p i = 0 p_i=0 pi=0代表 c u i < c v i c_{u_i}cui<cvi p i = 1 p_i=1 pi=1代表 c u i > c v i c_{u_i}>c_{v_i} cui>cvi u i u_i ui v i v_i vi父亲)。
可以发现一条路径给定了路径上相邻边之间的 p p p相等或相异的关系,可以简单建出边得到一个图,若得到的图不为二分图显然无解,否则可以将原树划分为若干连通块,第 i i i个连通块有个变量 q i q_i qi,连通块内部的 p p p均为 q i q_i qi ¬ q i \lnot q_i ¬qi
考虑二分答案 k k k,判定能否用 1 1 1 k k k覆盖所有的 c i c_i ci
显然可以得到一个dp,令 F [ i ] [ 0 / 1 ] [ j ] F[i][0/1][j] F[i][0/1][j]表示考虑了点 i i i的子树,点 i i i所在连通块的 v v v 0 / 1 0/1 0/1,且 c i = j c_i=j ci=j是否可行。直接实现复杂度过高,不过发现由于某条边的权值可以取任意正数,因此由儿子推到父亲后 v v v 0 / 1 0/1 0/1可行的 j j j 1 1 1 k k k的一个前缀或后缀(事实上 F [ i ] [ 0 / 1 ] [ j ] = 1 F[i][0/1][j]=1 F[i][0/1][j]=1 j j j形成至多两个区间),那么只需要维护 F [ i ] [ 0 / 1 ] [ j ] = 1 F[i][0/1][j]=1 F[i][0/1][j]=1的最小/最大的 j j j即可。并且注意到 v v v 0 / 1 0/1 0/1是对称的,只需维护 F [ i ] [ 0 ] F[i][0] F[i][0]即可。
构造方案也可以类似dp,时间复杂度为 O ( n log ⁡ n ) \mathcal O(n\log n) O(nlogn)

#include 
#define FR first
#define SE second

using namespace std;

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

inline pr merge(pr x,pr y) {
  return pr(max(x.FR,y.FR),min(x.SE,y.SE));
}

vector <int> e[500005];
int fa[500005][20],dep[500005];

void dfs1(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];
    	dfs1(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;
}

vector <pr> ee[500005];
int size[500005];

void dfs2(int x,int fa) {
  for(int i=0;i<e[x].size();i++)
    if (e[x][i]!=fa) {
    	int u=e[x][i];
    	dfs2(u,x);
    	size[x]+=size[u];
	}
  if (size[x]) {
  	ee[x].push_back(pr(fa,0));
  	ee[fa].push_back(pr(x,0));
  }
}

int col[500005],id[500005];

void dfs3(int x) {
  for(int i=0;i<ee[x].size();i++) {
  	int u=ee[x][i].FR;
  	if (!id[u]) {
  		id[u]=id[x];
  		col[u]=col[x]^ee[x][i].SE;
  		dfs3(u);
	  }
	else if ((col[x]^col[u])!=ee[x][i].SE) {
		puts("-1");
		exit(0);
	}
  }
}

pr f[500005],g[500005];
int vis[500005];

bool dfs4(int x,int fa,int k) {
  for(int i=0;i<e[x].size();i++)
    if (e[x][i]!=fa) {
    	int u=e[x][i];
    	if (!dfs4(u,x,k)) return 0;
	}
  f[x]=pr(1,k);
  int sz=0;
  for(int i=0;i<e[x].size();i++)
    if (e[x][i]!=fa&&id[e[x][i]]) {
    	int u=e[x][i];
    	if (id[u]==id[x]) {
    		if (!col[u]) f[x]=merge(f[x],pr(1,f[u].SE-1));
    		else f[x]=merge(f[x],pr(f[u].FR+1,k));
		}
		else {
			if (!vis[id[u]]) {
				vis[id[u]]=++sz;
				g[sz]=pr(1,k);
			}
			int t=vis[id[u]];
			if (!col[u]) g[t]=merge(g[t],pr(1,f[u].SE-1));
			else g[t]=merge(g[t],pr(f[u].FR+1,k));
		}
	}
  for(int i=0;i<e[x].size();i++)
    if (e[x][i]!=fa) vis[id[e[x][i]]]=0;
  if (f[x].FR>f[x].SE) return 0;
  pr u(1,k);
  for(int i=1;i<=sz;i++) {
  	if (g[i].FR>g[i].SE) return 0;
  	if (k-g[i].SE+1<g[i].FR) g[i]=pr(k-g[i].SE+1,k-g[i].FR+1);
  	u=merge(u,g[i]);
  }
  pr t1=merge(u,f[x]),t2=merge(pr(k-u.SE+1,k-u.FR+1),f[x]);
  if (t1.FR>t1.SE&&t2.FR>t2.SE) return 0;
  if (t1.FR>t1.SE) f[x]=t2;
  else if (t2.FR>t2.SE) f[x]=t1;
  else f[x]=pr(min(t1.FR,t2.FR),max(t1.SE,t2.SE));
  return 1;
}

int ans[500005],val[500005];
bool d[500005];

void dfs5(int x,int fa,int k,bool v) {
  int sz=0;
  for(int i=0;i<e[x].size();i++)
    if (e[x][i]!=fa&&id[e[x][i]]!=id[x]) {
    	int u=e[x][i];
		if (!vis[id[u]]) {
			vis[id[u]]=++sz;
			val[sz]=id[u];
			g[sz]=pr(1,k);
		}
		int t=vis[id[u]];
		if (!col[u]) g[t]=merge(g[t],pr(1,f[u].SE-1));
		else g[t]=merge(g[t],pr(f[u].FR+1,k));
	}
  for(int i=0;i<e[x].size();i++)
    if (e[x][i]!=fa) vis[id[e[x][i]]]=0;
  for(int i=1;i<=sz;i++) d[val[i]]=((g[i].FR<=ans[x]&&g[i].SE>=ans[x])?0:1);
  for(int i=0;i<e[x].size();i++)
    if (e[x][i]!=fa) {
    	int u=e[x][i];
    	if (!id[u]) {
    		ans[u]=f[u].FR;
    		dfs5(u,x,k,0);
		}
		else {
			bool c=((id[u]==id[x])?v:d[id[u]]);
			if (!c) ans[u]=((!col[u])?f[u].SE:f[u].FR);
			else ans[u]=((col[u])?k-f[u].FR+1:k-f[u].SE+1);
			dfs5(u,x,k,c);
		}
	}
} 

int main() {
  int n,m;
  scanf("%d%d",&n,&m);
  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);
  for(int i=1;i<=m;i++) {
  	int x,y;
  	scanf("%d%d",&x,&y);
  	int p=lca(x,y),u,v;
  	if (p!=x) {
  		size[x]++;
  		size[u=jump(x,dep[x]-dep[p]-1)]--;
	  }
	if (p!=y) {
		size[y]++;
		size[v=jump(y,dep[y]-dep[p]-1)]--;
	}
	if (p!=x&&p!=y) {
		ee[u].push_back(pr(v,1));
		ee[v].push_back(pr(u,1));
	}
  }
  dfs2(1,0);
  int cnt=0;
  for(int i=2;i<=n;i++)
    if (!id[i]) {
    	id[i]=++cnt;
    	dfs3(i);
	}
  int l=1,r=n;
  while (l<r) {
  	int mid=((l+r)>>1);
    bool v=dfs4(1,0,mid);
    if (v) r=mid; else l=mid+1;
  }
  dfs4(1,0,l);
  ans[1]=f[1].FR;
  dfs5(1,0,l,0);
  printf("%d\n",l);
  for(int i=1;i<=n;i++) printf("%d ",ans[i]);
  printf("\n");
  return 0;
} 
/*
3 1 
1 2
1 3
2 3 
*/ 

你可能感兴趣的:(codeforces,动态规划,数学)