Educational Codeforces Round 50

Educational Codeforces Round 50

codeforces

G. Sources and Sinks

description

有一张(DAG),定义source为入度为零的点,sink为出度为零的点。保证source和sink的个数相等且不超过(20) 。定义匹配一个source和一个sink为从sink向source连一条有向边。问是否任意一个source与sink之间的完美匹配都可以使原图变成强连通。

(nle10^6)

solution

设source与sink各有(C)个,那么只需要保证这(2C)个点组成的图强连通就行了。

有一个结论是:对于任意一个source的集合(S)(|S| eq0,|S| eq C) ,它能到达的sink集合是(f(S)) ,则需要有(|S|>|f(S)|)

复杂度(O(C(n+m)+C2^C))

code

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>
using namespace std;
int gi(){
	int x=0,w=1;char ch=getchar();
	while ((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
	if (ch=='-') w=0,ch=getchar();
	while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
	return w?x:-x;
}
const int N = 1e6+5;
int n,m,cnt[N<<1],to[N],nxt[N],head[N],in[N],val[N],C,vis[N],ans[20];
int dfs(int u){
	vis[u]=1;int res=val[u];
	for (int e=head[u];e;e=nxt[e])
		if (!vis[to[e]]) res|=dfs(to[e]);
	return res;
}
int main(){
	n=gi();m=gi();
	for (int i=1;i<=m;++i){
		int u=gi(),v=gi();
		to[i]=v;nxt[i]=head[u];head[u]=i;++in[v];
	}
	for (int i=1;i<=n;++i) if (!head[i]) val[i]=1<<C++;
	for (int i=1,j=0;j<C;++i) if (!in[i]) memset(vis,0,sizeof(vis)),ans[j++]=dfs(i);
	for (int i=0;i<(1<<C);++i) cnt[i]=cnt[i>>1]+(i&1);
	for (int i=1;i<(1<<C)-1;++i){
		int res=0;
		for (int j=0;j<C;++j) if (i>>j&1) res|=ans[j];
		if (cnt[i]>=cnt[res]) return puts("NO"),0;
	}
	return puts("YES"),0;
}

F. Relatively Prime Powers

description

将一个数质因数分解为(p_1^{a_1}p_2^{a_2}...p_k^{a_k}) ,定义其是elegent当且仅当(gcd(a_1,a_2...a_k)=1)

(T)组询问,每次询问(2-n)中elegent的数的个数。

(Tle10^5,nle10^{18})

solution

首先莫比乌斯随便演一下答案就是(sum_{i=1}^{infty}mu(i)(sqrt[i]n-1))

直接算的话复杂度(O(Tlog^2n))(每个质因子二分根号的那个值)可能过不了。

把询问离线,从大到小做,可以省去一个二分。

复杂度(O(Tlog T+Tlog n))

code

#include<cstdio>
#include<algorithm>
#include<cmath>
using namespace std;
#define ll long long
const int N = 1e5+5;
struct node{
	ll x;int y;
	bool operator < (const node &b) const
		{return x>b.x;}
}a[N];
int mu[61]={0,1,-1,-1,0,-1,1,-1,0,0,1,-1,0,-1,1,1,0,-1,0,-1,0,1,1,-1,0,0,1,0,0,-1,-1,-1,0,1,1,1,0,-1,1,1,0,-1,-1,-1,0,0,1,-1,0,0,0,1,0,-1,0,1,0,1,1,-1,0};
ll mx[61]={0,0,0,1000000,31623,3982,1000,373,178,100,67,46,33,25,20,16,13,11,10,9,8,7,6,6,5,5,5,4,4,4,4,3,3,3,3,3,3,3,3,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2};
int n;ll now[61],ans[N];
ll fastpow(ll a,int b){
	ll res=1;
	while (b) {if (b&1) res*=a;a*=a;b>>=1;}
	return res;
}
int main(){
	scanf("%d",&n);
	for (int i=1;i<=n;++i) scanf("%I64d",&a[i].x),a[i].y=i;
	sort(a+1,a+n+1);
	for (int i=3;i<=60;++i) now[i]=fastpow(mx[i],i);
	for (int i=1;i<=n;++i){
		mx[1]=a[i].x;mx[2]=sqrt(a[i].x);
		for (int j=3;j<=60;++j)
			while (now[j]>a[i].x) --mx[j],now[j]=fastpow(mx[j],j);
		for (int j=1;j<=60;++j) ans[a[i].y]+=1ll*(mx[j]-1)*mu[j];
	}
	for (int i=1;i<=n;++i) printf("%I64d
",ans[i]);
	return 0;
}

E. Covered Points

description

给你(n)条线段,问你有多少个点被这些线段覆盖了。

(nle1000,)坐标范围(le10^6)

solution

先不考虑算重,那么一条直线的贡献是(gcd(|x_1-x_2|,|y_1-y_2|)+1)

然后因为两条不重合直线至多一个交点,所以可以直接解出来交点,然后判重即可。

算交点直接联立两个一般式解方程,一般式用两点式求。

复杂度(O(n^2log n))

code

#include<cstdio>
#include<algorithm>
using namespace std;
int gi(){
	int x=0,w=1;char ch=getchar();
	while ((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
	if (ch=='-') w=0,ch=getchar();
	while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
	return w?x:-x;
}
#define ll long long
const int N = 1005;
struct seg{
	int x1,y1,x2,y2;
	int cal(){
		return __gcd(abs(x2-x1),abs(y2-y1))+1;
	}
	bool in(int x,int y){
		bool fg=1;
		if (x1<=x2) fg&=(x1<=x&&x<=x2);
		else fg&=(x2<=x&&x<=x1);
		if (y1<=y2) fg&=(y1<=y&&y<=y2);
		else fg&=(y2<=y&&y<=y1);
		return fg;
	}
}l[N];
int n,len,ans;pair<int,int>o[N];
struct line{
	ll A,B,C;
	line(){}
	line(seg a){
		A=a.y2-a.y1;B=a.x1-a.x2;
		C=-A*a.x1-B*a.y1;
	}
};
bool cross(int i,int j,int &X,int &Y){
	line a(l[i]),b(l[j]);
	ll x=a.B*b.C-a.C*b.B,y=-a.A*b.C+a.C*b.A,z=a.A*b.B-a.B*b.A;
	if (!z) return false;if (x%z||y%z) return false;
	X=x/z;Y=y/z;
	if (l[i].in(X,Y)&&l[j].in(X,Y)) return true;return false;
}
int main(){
	n=gi();
	for (int i=1;i<=n;++i){
		l[i]=(seg){gi(),gi(),gi(),gi()};
		ans+=l[i].cal();len=0;
		for (int j=1,x,y;j<i;++j)
			if (cross(i,j,x,y)) o[++len]=make_pair(x,y);
		sort(o+1,o+len+1);len=unique(o+1,o+len+1)-o-1;ans-=len;
	}
	printf("%d
",ans);return 0;
}

D. Vasya and Arrays

description

给你两个数组(A,B) ,你的每次操作是可以把连续一段数合在一起,值为这些数的和。

求把两个数组变成相同后数组的最长长度。

(nle3 imes10^5,1le a_i,b_ile10^9)

solution

贪心,每次用双指针找到两个数组最短的相等前缀。复杂度线性。

code

#include<cstdio>
#include<algorithm>
using namespace std;
int gi(){
	int x=0,w=1;char ch=getchar();
	while ((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
	if (ch=='-') w=0,ch=getchar();
	while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
	return w?x:-x;
}
const int N = 3e5+5;
int a[N],b[N];
int main(){
	int n=gi(),i,j,ans=0;
	for (i=1;i<=n;++i) a[i]=gi();
	int m=gi();
	for (i=1;i<=m;++i) b[i]=gi();
	long long sa=a[1],sb=b[1];
	for (i=1,j=1;i<=n&&j<=m;){
		if (sa==sb) ++ans,sa=a[++i],sb=b[++j];
		else if (sa>sb) sb+=b[++j];
		else sa+=a[++i];
	}
	while (i<n) sa+=a[++i];
	while (j<m) sb+=b[++j];
	if (sa==sb) printf("%d
",ans);
	else puts("-1");
	return 0;
}

C. Classy Numbers

description

定义一个数是classy的当且仅当其在十进制表示下至多有(3)个非零位。

(T)组询问,每次问([L_i,R_i])之间有多少个classy的数。

(Tle10^4,L_i,R_ile10^18)

solution

可以直接数位dp。

我比较懒,用了另一种方法:拿组合数算一下会发现满足条件的数只有60w+个,所以就爆搜出来再二分就行了。

code

#include<cstdio>
#include<algorithm>
using namespace std;
#define ll long long
ll o[1000005],L,R;int T,len;
void dfs(int i,int j,ll sum){
	if (i==18) {o[++len]=sum;return;}
	dfs(i+1,j,sum*10);
	if (j<3)
		for (int k=1;k<=9;++k)
			dfs(i+1,j+1,sum*10+k);
}
int main(){
	dfs(0,0,0);o[++len]=1e18;
	scanf("%d",&T);while (T--){
		scanf("%I64d%I64d",&L,&R);
		printf("%d
",upper_bound(o+1,o+len+1,R)-lower_bound(o+1,o+len+1,L));
	}
	return 0;
}

B. Diagonal Walking v.2

description

(q)组询问,每次从((0,0))点出发走向((n,m)),每一步可以走到横纵坐标(pm1)的位置(不能不走,也就是有(8)种走法)要求恰好走(k)步,求最多可以斜着走多少步。

(qle10^4,n,m,kle10^{18})

solution

走法大体上分成两个阶段(假设(n>m)):第一阶段横着走到((n-m,0)),当然这里的横着走指的是一上一下,即在走了偶数步后结果上还是横着走的;第二阶段斜着走到((n,m))

步数可能会多,多余的偶数步可以在终点消耗掉,并且都可以算贡献。现在只需要考虑怎么消耗掉一步:

如果要在第一阶段消耗掉一步,必须要(n-m)是奇数,这样等效于把起点移到((1,0)) ,同时步数减(1)

如果要在第二阶段消耗掉一步,必须要(n-k)是奇数,这样等效于把起点移到((1,1)) ,同时步数减(2)

所以优先在第一阶段消耗掉这一步,这样的话答案就是(k)了。

code

#include<cstdio>
#include<algorithm>
using namespace std;
#define ll long long
ll gi(){
	ll x=0,w=1;char ch=getchar();
	while ((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
	if (ch=='-') w=0,ch=getchar();
	while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
	return w?x:-x;
}
int main(){
	int q=gi();while (q--){
		ll x=gi(),y=gi(),k=gi();if (x<y) swap(x,y);
		if ((x&1)^(y&1)) --k,--x;
		else if ((x&1)^(k&1)) k-=2,--x,--y;
		printf("%I64d
",k<x?-1:k);
	}
	return 0;
}

A. Function Height

description&solution

输出(lceilfrac nk ceil)

code

#include<cstdio>
#include<algorithm>
using namespace std;
int main(){
	long long n,k;
	scanf("%I64d%I64d",&n,&k);
	printf("%I64d
",(k+n-1)/n);
	return 0;
}
原文地址:https://www.cnblogs.com/zhoushuyu/p/9651368.html