Codeforces Round #439 (Div. 2)

一句话题意:
A:传送门
题意:给定两个长为(n)的数组(a)(b),令(ans=)有序对((i,j))的个数使得(a_i xor b_j)在这(2n)个数中出现过,然后如果(ans)是奇数就输出Koyomi,否则输出Karen,(nleq{2000},a_i,b_ileq2000000)
B:传送门
题意:求({b!over{a!}} mod 10)(0leq{a}leq{b}leq{10^{18}})
C:传送门
题意:有(a)个红色的点,(b)个蓝色的点,(c)个紫色的点,在这些点之间任意连长度为(1)的边,保证同颜色的点之间不能连边且同颜色的点之间的最短路径至少为(3),求方案数(mod 998244353)(a,b,cleq5000)
D:传送门
题意:一个有(n)个节点的树,(i)(iover2)之间有一条边,另外有(m)条边,每条边连接树上的两个点,求这个图的简单路径条数(mod 10^9+7)(nleq{10^9},0leq{m}leq4)
E:传送门
题意:有一个(n imes{m})的矩形,(q)个询问,询问中有三种操作,(1 r_1 c_1 r_2 c_2)是把以((r_1,c_1))((r_2,c_2))为两个对角的矩形的四周加上围栏(围栏具体长啥样可以参照样例解释),(2 r_1 c_1 r_2 c_2)是把以((r_1,c_1))((r_2,c_2))为两个对角的矩形的四周的围栏去掉(保证以前出现过),(3 r_1 c_1 r_2 c_2)是询问点((r_1,c_1))((r_2,c_2))能否不跨越围栏而相互到达,保证围栏不会和这个矩形的边界重叠,两次设下的围栏之间不会相交更不会重叠。(n,mleq2500,qleq100000)
至于为什么先给出题意,相信有很多dalao只是来看题意的。。。。
下面是题解
A:
思路:直接(n^2)暴力,开个桶记录一下每个数是否出现过就好了。

#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define maxn 2005

int n,m,ans;
int a[maxn],b[maxn];
bool fuck[10000005];

inline int read(){
	int x=0,f=1; char ch=getchar();
	for (;ch<'0'||ch>'9';ch=getchar()) if (ch=='-') f=-1;
	for (;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0';
	return x*f;
}

inline void print(int x){
	if (x<0){putchar('-'); x=-x;}
	if (x>=10) print(x/10);
	putchar(x%10+'0');
}

int main(){
	n=read();
	for (int i=1;i<=n;i++) a[i]=read(),fuck[a[i]]=1;
	for (int i=1;i<=n;i++) b[i]=read(),fuck[b[i]]=1;
	for (int i=1;i<=n;i++)
		for (int j=1;j<=n;j++)
			if (fuck[a[i]^b[j]]) ans++;
	puts((ans&1)?"Koyomi":"Karen");
	return 0;
}

B:
思路:考虑乘的数中如果一旦出现(10)的倍数答案就是(0),所以如果(b-ageq10)答案就是(0),否则直接暴力就好了。

#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;

int n,m;

inline int read(){
	int x=0,f=1; char ch=getchar();
	for (;ch<'0'||ch>'9';ch=getchar()) if (ch=='-') f=-1;
	for (;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0';
	return x*f;
}

inline void print(int x){
	if (x<0){putchar('-'); x=-x;}
	if (x>=10) print(x/10);
	putchar(x%10+'0');
}

long long a,b;

int main(){
	scanf("%lld%lld",&a,&b);
	if (b-a>20){puts("0"); return 0;}
	int ans=1;
	for (long long i=a+1;i<=b;i++) ans=ans*(i%10)%10;
	printf("%d
",ans);
	return 0;
}

C:
思路:其实同种颜色之间的最短路径至少是(3)的意思就是一个点不能向某一种非这个点颜色的点集连两条边。然后我们可以发现蓝色与紫色之间的连边对蓝色与红色之间的连边并没有影响,所以我们可以把它们分开来计算。
比如当前我们考虑两种颜色,一种点数为(n),另一种点数为(m),钦定(n<m),然后考虑之间的连边数,其实就是把一种颜色看成左部点,另一种颜色看成右部点,然后就成了一个二分图,求匹配的方案数,然后我们可以枚举最后比如匹配了(i)条边,那么我们就相当于是从(n)个点中选出(i)个点,然后选出的第一个点有(m)种方案匹配,第二个点因为第一个点已经匹配了一个所以就只有(m-1)种方案,以此类推,所以最后的结果就是$$sum_{i=0}nC_ni{m!over{(m-i)!}}$$然后两两计算完之后的方案乘起来就好了。

#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define maxn 5005
const int mod=998244353;

int n,m,k,ans;
int inv[maxn],fac[maxn];
int C[maxn][maxn];

inline int read(){
	int x=0,f=1; char ch=getchar();
	for (;ch<'0'||ch>'9';ch=getchar()) if (ch=='-') f=-1;
	for (;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0';
	return x*f;
}

inline void print(int x){
	if (x<0){putchar('-'); x=-x;}
	if (x>=10) print(x/10);
	putchar(x%10+'0');
}

int solve(int n,int m){
	int ret=0; if (n>m) swap(n,m);
	for (int i=0;i<=n;i++) ret=(ret+1ll*C[n][i]*fac[m]%mod*inv[m-i])%mod;
	return ret;
}

int main(){
	n=read(),m=read(),k=read();
	fac[0]=1; for (int i=1;i<=5000;i++) fac[i]=1ll*fac[i-1]*i%mod;
	inv[0]=inv[1]=1; for (int i=2;i<=5000;i++) inv[i]=1ll*(mod-mod/i)*inv[mod%i]%mod;
	for (int i=1;i<=5000;i++) inv[i]=1ll*inv[i-1]*inv[i]%mod;
	for (int i=0;i<=5000;i++){
		C[i][0]=1;
		for (int j=1;j<=i;j++) C[i][j]=(C[i-1][j-1]+C[i-1][j])%mod;
	}
	ans=1ll*solve(n,m)*solve(m,k)%mod*solve(n,k)%mod;
	printf("%d
",ans);
	return 0;
}

D:
思路:这个题首先感觉是个十分恶心的tree dp,然后看了一下数据范围发现tree dp不可捉,然后我就YY建出一棵类似虚树的环套树,然后大力dp,然后发现也非常不可捉(好吧这个应该是能做的,只不过我菜写不出而已。。。。),然后看了一发tutorial,咦,怎么这么暴力。。。。。然后我冷静了一下,发现这个类似虚树的玩意的size是(O(mlogn))的,然后直接大力dfs就好了。。。。。。
具体做法就是把环上的每个点都提出来建一个图,这个图的size是(O(mlogn))的,所以就可以大力枚举起点,然后dfs在环上有哪些路径,方案数的话再乘以环上的每个点对应的树的size就好了。。。然后我老实地写了个hash_table。。。。。

#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define maxn 100005
const int mod=1e9+7;

int n,m,pps,tot;
int size[maxn],now[maxn],pre[maxn],son[maxn],num[40];
bool vis[maxn];

inline int read(){
	int x=0,f=1; char ch=getchar();
	for (;ch<'0'||ch>'9';ch=getchar()) if (ch=='-') f=-1;
	for (;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0';
	return x*f;
}

int calc(int x){
	int d1=1,d2=1,t1,t2;
	for (t1=x;t1<=n;t1<<=1,d1++); t1>>=1,d1--;
	for (t2=x;t2<=n;t2=t2<<1|1,d2++); t2>>=1,d2--;
	return num[min(d1,d2)]+(d1==d2?0:(n-t1+1));
}

struct hash_table{
	static const int mod=(1<<20);
	int tot,now[mod+10],pre[maxn],son[maxn];
	void insert(int x){
		int tmp=x&(mod-1);
		son[++tot]=x,pre[tot]=now[tmp],now[tmp]=tot,size[tot]=calc(x);
	}
	int find(int x){
		int tmp=x&(mod-1);
		for (int p=now[tmp];p;p=pre[p]) if (son[p]==x) return p;
		return 0;
	}
}h;

struct edge{int x,y;}e[maxn];

void add(int a,int b){son[++tot]=b,pre[tot]=now[a],now[a]=tot;}
void link(int a,int b){add(a,b),add(b,a);}

void get_size(int x,int fa){
	for (int p=now[x];p;p=pre[p])
		if (son[p]!=fa) size[x]-=size[son[p]],get_size(son[p],x);
}

void dfs(int x,int &ret){
	vis[x]=1,ret=(ret+size[x])%mod;
	for (int p=now[x];p;p=pre[p])
		if (!vis[son[p]]) dfs(son[p],ret);
	vis[x]=0;
}

int solve(int x){
	int ret=0;
	dfs(x,ret);
	return ret;
}

int main(){
	pps=n=read(),m=read();
	for (int i=1;i<=30;i++) num[i]=2*num[i-1]+1;
	h.insert(1);
	for (int i=1;i<=m;i++){
		int x=read(),y=read(); e[i]=(edge){x,y};
		for (int t=x;t;t>>=1) if (!h.find(t)) h.insert(t);
		for (int t=y;t;t>>=1) if (!h.find(t)) h.insert(t);
	}
	for (int i=2;i<=h.tot;i++) link(i,h.find(h.son[i]>>1));
	get_size(1,0);
	for (int i=1;i<=m;i++) link(h.find(e[i].x),h.find(e[i].y));
	int ans=0;
	for (int i=1;i<=h.tot;i++) ans=(ans+1ll*solve(i)*size[i])%mod;
	printf("%d
",ans);
	return 0;
}

E:
思路:由于围栏之间不能相交也不会重叠,于是我们就可以脑补一下,然后发现这可以类比成物理中的等势面或地理中的等高线地形图,是不是非常形象呢233333333
然后每一次加围栏我们看成是给一个矩形加一个权值,然后可以发现能够互相到达的点一定是权值相等的两个点,但权值相等的两个点却不一定能够互相到达,有一种情况会GG,脑补一下如果两个点位于两个山峰上,那么显然它们不可互达,但如果权值相等的话它们就会被认为是可以互达的,所以如果权值没设好就有可能会GG,其实你随机一下就好了,每个操作随机来个权值,然后用一个二维树状数组资瓷区间加减(差分一下)以及单点查询就好了。
然后考试的时候我被map坑了。。。。当你要删的时候你要减去的权值是你添加这条围栏时候的随机值,所以你就需要一个map或者hash_table,但我懒不想写hash_table,所以用了个map,然后operator瞎捷豹写了一个因为不清楚map具体怎么实现的,以为随便来个operator都可以,然后就GG了。。。。这真是一个悲伤的故事。。。。

#include<map>
#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define maxn 2505

int n,m,cnt,q;
long long c[maxn][maxn];

struct node{
	int a,b,c,d; node(){} node (int _a,int _b,int _c,int _d){a=_a,b=_b,c=_c,d=_c;}
	bool operator <(const node &x)const{
		if (a!=x.a) return a<x.a;
		if (b!=x.b) return b<x.b;
		if (c!=x.c) return c<x.c;
		if (d!=x.d) return d<x.d;
		return 0;
	}
};

map<node,int> mp;

inline int read(){
	int x=0,f=1; char ch=getchar();
	for (;ch<'0'||ch>'9';ch=getchar()) if (ch=='-') f=-1;
	for (;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0';
	return x*f;
}

inline void print(int x){
	if (x<0){putchar('-'); x=-x;}
	if (x>=10) print(x/10);
	putchar(x%10+'0');
}

void change(int x,int y,int v){
	for (int i=x;i<=n;i+=i&(-i))
		for (int j=y;j<=m;j+=j&(-j))
			c[i][j]+=v;
}

long long query(int x,int y){
	long long ret=0;
	for (int i=x;i;i-=i&(-i))
		for (int j=y;j;j-=j&(-j))
			ret+=c[i][j];
	return ret;
}

int get_rand(int x){
	return 1ll*rand()*rand()%1000000000+x;
}

int main(){
	n=read(),m=read(),q=read();
	for (int i=1;i<=q;i++){
		int type=read(),x1=read(),y1=read(),x2=read(),y2=read();
		if (type==3){
			long long x=query(x1,y1),y=query(x2,y2);
			puts(x==y?"Yes":"No");
		}
		if (x1>x2) swap(x1,x2); if (y1>y2) swap(y1,y2);
		if (type==1){
			int pps=0;
			if (mp.find(node(x1,y1,x2,y2))==mp.end()) pps=get_rand(i);
			else pps=mp[node(x1,y1,x2,y2)];
			change(x1,y1,pps);
			change(x2+1,y1,-pps);
			change(x1,y2+1,-pps);
			change(x2+1,y2+1,pps);
			mp[node(x1,y1,x2,y2)]=pps;
		}
		if (type==2){
			int pps=mp[node(x1,y1,x2,y2)];
			change(x1,y1,-pps);
			change(x2+1,y1,pps);
			change(x1,y2+1,pps);
			change(x2+1,y2+1,-pps);
		}
	}
	return 0;
}
原文地址:https://www.cnblogs.com/DUXT/p/7635072.html