CF Edu Round 85 (Rated for div.2)

\(Educational\) \(Codeforces\) \(Round\) \(85\) \((Rated\) \(for\) \(Div.2)\)

A. Level Statistics

\(Description:\)

给定两个数组\(a_n\)\(b_n\),要求两个数组都递增,而且第二个比第一个增长缓慢

\(Solution:\)

签到题,扫一遍的时候判断一下就行了

\(Code:\)

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;
int t,n,a[105],b[105];
int main(){
	int i,j;
	scanf("%d",&t);
	while(t--){
		scanf("%d",&n);
		for(i=1;i<=n;i++)scanf("%d%d",&a[i],&b[i]);
		int flag=1;
		for(i=1;i<=n;i++){
			if(a[i]<a[i-1]||b[i]<b[i-1])flag=0;
			if(b[i]-b[i-1]>a[i]-a[i-1])flag=0;
		}
		if(flag)printf("YES\n");
		else printf("NO\n");
	}
	return 0;
}

B. Middle Class

\(Description:\)

总共\(n\)个人,每个人的初始财产为\(a_n\),令财产大于\(x\)的为富人
选取任意多个人,将他们的财产均分,使得富人最多

\(Solution:\)

排序一遍,从大到小找尽可能多的人进行均分,显然这样的贪心策略是正确的

\(Code:\)

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;
int t,n;
double x,a[100005];
bool cmp(const double a,const double b){return a>b;}
int main(){
	int i,j;
	scanf("%d",&t);
	while(t--){
		scanf("%d%lf",&n,&x);
		for(i=1;i<=n;i++)scanf("%lf",&a[i]);
		sort(a+1,a+n+1,cmp);
		double sum=0,ave;
		int ans=0;
		for(i=1;i<=n;i++){
			sum+=a[i];
			ave=sum/i;
			if(ave>=x)ans=i;
		}
		printf("%d\n",ans);
	}
	return 0;
}

C. Circle of Monsters

\(Description:\)

\(n\)个怪物围成一个环,每个怪物有\(a_i\)的血,死后会对其后面的怪兽造成\(b_i\)的伤害
每次可以用一个子弹打掉任意一个怪兽的一滴血,问最少多少颗子弹能把所有怪兽打死

\(Solution:\)

1.如果\(b_{i-1}\)\(\geq\)\(a_i\),那么第\(i\)个怪兽显然不需要打,打了也是浪费子弹,因为第\(i-1\)个怪兽迟早都是要死的
2.如果\(b_{i-1}\)\(<\)\(a_i\),那么\(a_i-b_{i-1}\)那一部分的血量肯定是要用子弹打的
3.由于是一个环,所以得找一个怪兽开始打,第一个死的怪兽肯定是用枪打死的,所以就枚举一开始要打哪个怪兽即可

\(Code:\)

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#define ll(x) ((x-1)?(x-1):n)
#define rr(x) ((x==n)?(1):(x+1))
using namespace std;
typedef long long lol;
int t,n;
lol a[300005],b[300005];
int main(){
	int i,j;
	scanf("%d",&t);
	while(t--){
		scanf("%d",&n);
		lol mmin=1e12,ans=0;
		for(i=1;i<=n;i++)scanf("%lld%lld",&a[i],&b[i]);
		for(i=1;i<=n;i++){ans+=max(0ll,a[i]-b[ll(i)]);mmin=min(mmin,a[i]-max(0ll,a[i]-b[ll(i)]));}
		printf("%lld\n",ans+mmin);
	}
	return 0;
}

D. Minimum Euler Cycle

\(Description:\)

给定一个完全图(双向),找到一条经过单向边的路径,使得它经过节点的字典序最小
输出这条路径所经过的\(l\) ~ \(r\)节点

\(Solution:\)

手动模拟几次后发现以下规律:
每次都是从\(1\)开始,然后到其他节点然后立即回来,到达\(n\)时转到\(2\)节点
然后每次从\(2\)开始,以此到\(3\)\(4\)...然后立即回来,到达\(n\)时转到\(3\)节点
以此类推……
每次转到下一个起点时会经过\(2*(n-i)\)个点,由此可以迅速确定\(l\)的位置,然后依次输出即可

\(Code:\)

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;
typedef long long lol;
lol t,n,l,r;
int main(){
	lol i;
	scanf("%lld",&t); 
	while(t--){
		scanf("%lld%lld%lld",&n,&l,&r);
		lol tot=n*(n-1)+1,cnt=0,id=1;
		for(i=l;i<=r&&i<tot;i++){
			while(cnt+2*(n-id)<i){cnt+=2*(n-id);id++;}
			lol need=i-cnt;
			printf("%d ",(need&1)?id:id+need/2);
		}
		if(r==tot)printf("1 ");
		printf("\n");
	}
	return 0;
}

E. Divisor Paths

\(Description:\)

给定一个数\(D\),建立一个图,图的每个节点是\(D\)的约数,若两个节点的商为素数,那么就在这两点之间连一条边,边权为两个数的约数个数差。求这个图的任意两点间的最短路条数。

\(Solution:\)

\(D\)进行质因数分解

\[D={p_1}^{x_1}+{p_2}^{x_2}+...+{p_n}^{x_n}(p_n\in prime,p_i\not= p_j)\]

于是节点个数,也就是\(D\)的约数个数为$$(x_1+1)(x_2+1)...(x_n+1)$$
于是我们将每一个节点\(i\)表示为$$(x_{i_1},x_{i_2},...,x_{i_n}) (x_{i_j}\leq x_j)$$
那么任意两个节点的最短路$$dis=|x_{i_1}-x_{j_1}|+|x_{i_2}-x_{j_2}|+...+|x_{i_n}-x_{j_n}|$$
相当于每次调整可以将当前数乘上或除以一个\(D\)的质因数\(p\),最少需要\(dis\)步才能将\(i\)调整为\(j\)
不难发现\(dis\)与调整的先后顺序无关,只跟\(i\)\(j\)的质因数分解式有关,因而调整的顺序是千变万化的
令任意两个节点\(i\)\(j\)最短路径的条数为\(N(i,j)\),那么\(N\)即为上述\(i\)\(j\)调整顺序的总数
若i和j之间有整除关系,那么由组合数可以推导出$$N(i,j)=\frac{Sum!}{m_1!·m_2!·...·m_n!}(m_i=|x_{i_1}-x_{j_1}|,Sum=\sum_{i=1}^{n}m_i)$$
若无整除关系,为计算\(N(i,j)\),我们引进\(k=gcd(i,j)\)并将其分解,即$$k=gcd(i,j)=(x_{k_1},x_{k_2},...,x_{k_n})(x_{k_s}\leq x_{i_s},x_{k_s}\leq x_{j_s})$$
那么\(N(i,j)=N(i,k)·N(k,j)\),即将\(i\)调整为\(gcd(i,j)\),再将\(gcd(i,j)\)调整为\(j\)
容易证明这样做是对的,因为如果不经过\(gcd(i,j)\),那么一定不是最短路,因为额外增加一个质因子只会让某条路径上两端的节点的约数个数差更大,这样就必然不是最短路

\(Code:\)

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<queue>
#define mod (998244353)
using namespace std;
typedef long long lol;
lol D,DD,u,v,q,cnt,di[55],C[55][55];
lol gcd(lol a,lol b){return b?gcd(b,a%b):a;}
lol solve(lol x){
	lol i,j,sum=0,cnt2=0,num[55]={0},ans=1;
	for(i=1;i<=cnt;i++){
		if(x%di[i]==0){
			cnt2++;
			while(x%di[i]==0){num[cnt2]++;x/=di[i];}
		}
	}
	for(i=1;i<=cnt2;i++)sum=sum+num[i];
	for(i=1;i<=cnt2;i++){(ans*=C[sum][num[i]])%=mod;sum-=num[i];}
	return ans;
}
int main(){
	lol i,j;
	scanf("%lld",&D);DD=D;
	for(i=2;i*i<=D;i++){
		if(DD%i==0)di[++cnt]=i;
		while(DD%i==0)DD/=i;
	}
	if(DD!=1)di[++cnt]=DD;
	for(i=0;i<=50;i++){
		C[i][0]=1;
		for(j=1;j<=i;j++)
			C[i][j]=(C[i-1][j-1]+C[i-1][j])%mod;	
	}
	scanf("%lld",&q);
	while(q--){
		scanf("%lld%lld",&u,&v);
		printf("%lld\n",solve(u/gcd(u,v))*solve(v/gcd(u,v))%mod);
	}
	return 0;
}

F. Strange Function

\(Description:\)

定义序列函数\(f(x)\):满足\(x_i>x_{1...i-1}\)\(x_i\)组成的序列,如:\(f[3,1,2,7,7,3,6,7,8]=[3,7,8]\)
给出三个序列\(a,p,b\),删除\(a_i\)的代价为\(p_i\)\((\)\(p_i\)可能为负\()\)。求使\(f(a)=b\)的最小代价。无解输出 \(NO\)

\(Solution:\)

原文链接
显然如果\(b\)不是\(a\)的子序列肯定无解,否则一定有解
按照\(a_i\)从小到大\(DP\),设\(f_i\)为以\(i\)结尾的最小代价:
1.如果\(a_i\notin b\)则忽略。
2.如果\(a_i\in b\),设\(a_i=b_j\),则\(f_i=\min_{x=1}^{i-1}(f_x+\operatorname{contri}(x+1,i-1,b_{j-1}))[a_x=b_{j-1}]\)。其中\(\operatorname{contri}(l,r,v)\)为区间\([l,r]\)需要被删除的数之和,即\(i\in[l,r]\)中所有满足\(a_i\geq v或q_i<0\)\(q_i\)之和,可以用主席树求出。
主席树常数大又难写?考虑到\(\operatorname{contri}(l,r,b_{j-1})\)\(\operatorname{contri}(l,r,b_j)\)之间只改变了所有满足\(b_{j-1}<a_x\leq b_j\)\(q_x\)的贡献,用树状数组维护即可。
可以看出这样\(DP\)\(O(n^2\log n)\)的。其实并不需要枚举所有\(x\),求\(f_i\)时:
如果这是\(b_j\)第一次在\(a\)出现,直接按照上面的方程\(DP\)可。
否则设\(b_j\)前一次出现在\(a_y\)(即\(y<i\)\(a_y=b_j\)),则一开始直接计算\(f_i=f_y+\operatorname{contri}(y,i-1)\)就可以替代所有小于\(y\)\(a_x=b_{j-1}\)的转移。
这样每个数只会被转移\(1\)次,单次时间复杂度\(O(\log n)\)(计算\(\operatorname{contri}\)\(\log\))。
需要注意边界条件,可以适当设置边界值方便写代码。

\(Code:\)

#include<bits/stdc++.h>
using namespace std;
typedef long long lol;
const int N=500005;
lol c[N],f[N];
int n,m,a[N],b[N],p[N];
vector<int>pos[N];
void insert(int x,int v){for(int i=x;i<=n;i+=i&-i)c[i]+=v;}
lol query(int x){lol ans=0;for(int i=x;i;i-=i&-i)ans+=c[i];return ans;}
lol cal(int u,int v){return query(v)-query(u-1);}
int main(){
	int i,j,k;
	scanf("%d",&n);
	for(i=1;i<=n;i++){scanf("%d",&a[i]);pos[a[i]].push_back(i);}
	for(i=1;i<=n;i++){scanf("%d",&p[i]);insert(i,p[i]);}
	scanf("%d",&m);
	for(i=1;i<=m;i++)scanf("%d",&b[i]);
	int place=0;
	for(i=1;i<=m;i++){
		int pp=lower_bound(pos[b[i]].begin(),pos[b[i]].end(),place)-pos[b[i]].begin();
		if(pp==pos[b[i]].size()){printf("NO\n");return 0;}
		place=pos[b[i]][pp]+1;
	}
	printf("YES\n");
	memset(f,0x3f,sizeof(f));
	b[0]=f[0]=0;b[++m]=n+1;pos[0].push_back(0);pos[n+1].push_back(n+1);
	for(i=1;i<=m;i++){
		if(i>1)
			for(j=b[i-2]+1;j<=b[i-1];j++)
				for(auto k:pos[j])if(p[k]>0)insert(k,-p[k]);
		int place=0,size=pos[b[i-1]].size()-1;
		for(j=0;j<pos[b[i]].size();j++){
			k=pos[b[i]][j];
			if(j)f[k]=f[pos[b[i]][j-1]]+cal(pos[b[i]][j-1],k-1);
			while(place<=size&&pos[b[i-1]][place]<k){
				int pp=pos[b[i-1]][place++];
				f[k]=min(f[k],f[pp]+cal(pp+1,k-1));
			}
		}
	}
	printf("%lld\n",f[n+1]);
	return 0;
}

咕咕咕

BBT

本蒟蒻菜的一批

posted @ 2020-04-13 11:10  kakakakakaka  阅读(238)  评论(0编辑  收藏  举报

Never forget why you start

//鼠标爆炸特效