比赛记录:Codeforces Round 940 (Div. 2) and CodeCraft-23 A~E

53 篇文章 0 订阅
11 篇文章 1 订阅

传送门:CF

[前题提要]:感觉这场题目其实都很经典.遗憾的是赛时C题答案统计看成了不同的下棋的方案数,然后以为刚开始不能放不是一种答案(直接特判输出了0),卡了一场比赛.幸好最后15min险过CD,不然掉大分了


A. Stickogon

难点在于读懂题意.
读懂题意之后不难发现每根木棒都形成三角形是最优的.所以最终答案就是所有数字除3的贡献总和.

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define root 1,n,1
#define ls (rt<<1)
#define rs (rt<<1|1)
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
inline ll read() {
	ll x=0,w=1;char ch=getchar();
	for(;ch>'9'||ch<'0';ch=getchar()) if(ch=='-') w=-1;
	for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0';
	return x*w;
}
inline void print(__int128 x){
	if(x<0) {putchar('-');x=-x;}
	if(x>9) print(x/10);
	putchar(x%10+'0');
}
#define maxn 1000000
const double eps=1e-8;
#define	int_INF 0x3f3f3f3f
#define ll_INF 0x3f3f3f3f3f3f3f3f
int a[maxn];
int main() {
	int T=read();
	while(T--) {
		int n=read();
		for(int i=1;i<=n;i++) {
			int num=read();
			a[num]++;
		}
		int ans=0;
		for(int i=1;i<=100;i++) {
			ans+=a[i]/3;
		}
		cout<<ans<<endl;
		for(int i=1;i<=100;i++) {
			a[i]=0;
		}
	}
	return 0;
}

B. A BIT of a Construction

构造题.
刚开始我的想法是给每一个数都先分配一个1(不同位置),然后将剩下来的数字再分配一下.但是想了想这中想法似乎不是很好实现.想到B题一般都是小清新题.所以开始手玩找规律.
考虑这样构造,找到 k k k二进制位中第一个 1 1 1,位置是 i i i,然后第一个数字就是 2 i − 1 2^i-1 2i1,第二个数字就是 k − ( 2 i − 1 ) k-(2^i-1) k(2i1),剩余数字都为0.下面来证明一下这种构造方法的正确性.

我们会发现如果 k k k的所有二进制位都是1,此时我们的总贡献是就是 k k k的二进制位数.并且我们会发现, k k k的最大贡献也只能是它的二进制位数,不可能比它还大(不然总和就大于k了).
如果 k k k的所有二进制位有0,此时我们的构造贡献是 k k k的二进制位数-1.此时我们会发现, k k k的最大贡献不可能是它的二进制位数(不然此时总和就比 k k k大了),所以此时我们的贡献依旧是最优的.

证毕.

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define root 1,n,1
#define ls (rt<<1)
#define rs (rt<<1|1)
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
inline ll read() {
	ll x=0,w=1;char ch=getchar();
	for(;ch>'9'||ch<'0';ch=getchar()) if(ch=='-') w=-1;
	for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0';
	return x*w;
}
inline void print(__int128 x){
	if(x<0) {putchar('-');x=-x;}
	if(x>9) print(x/10);
	putchar(x%10+'0');
}
#define maxn 1000000
const double eps=1e-8;
#define	int_INF 0x3f3f3f3f
#define ll_INF 0x3f3f3f3f3f3f3f3f
int a[maxn];
int main() {
	int T=read();
	while(T--) {
		int n=read();int k=read();
		if(n==1) {
			cout<<k<<endl;
			continue;
		}
		int pos=-1;
		for(int i=30;i>=0;i--) {
			if(k&(1<<i)) {
				pos=(1<<i);break;
			}
		}
		cout<<pos-1<<" "<<k-(pos-1)<<" ";
		for(int i=3;i<=n;i++) {
			cout<<0<<" ";
		}
		cout<<endl;
	}
	return 0;
}

C. How Does the Rook Move?

经典递推.
一看到取模.不难想到计数dp.所以考虑如何从一个大问题转化为一个小问题.手玩一下后会发现当我们随便选取一些点之后,我们的大正方形被切割了,但是此时我们的贡献依旧是我们将切割的部分合并起来的正方形的贡献.也就是如何分割并不会影响我们的剩余贡献.并且我们会发现我们选取一个正对角线上的点,我们剩余正方形大小-1;选取一个不在正对角线上的点,我们剩余正方形大小-2.
考虑设 d p [ i ] dp[i] dp[i]表示边长为 i i i的正方形在没有任意初始选点的情况下的最终不同情况数.那么设初始选在正对角线上的点的个数为cnt1,其他选点为cnt2.那么根据上述推论,我们最终的答案就是 d p [ n − c n t 1 − 2 ∗ c n t 2 ] dp[n-cnt1-2*cnt2] dp[ncnt12cnt2].显然的,现在我们的问题就变成了如何预处理dp数组.
在这里插入图片描述
以4为例,我们只需要枚举上述几个打钩的点作为我们刚开始的起点即可.为什么呢,因为你发现假设你刚开始选其他点作为起点,最后的所有情况其实和上述是重复的.为什么会出现这种情况呢,其实细想一下就会发现因为对于其他点来说,那些点和我们上述打钩的点中一定存在一个点是互不干扰的,也就是此时我们选取这个点,和先选取打钩的其中的一个点对最终情况的贡献是相等的(如果感觉讲的不是很清晰,可以自己手画一下).
所以我们就不难得出最终的递推公式了.

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define root 1,n,1
#define ls (rt<<1)
#define rs (rt<<1|1)
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
inline ll read() {
	ll x=0,w=1;char ch=getchar();
	for(;ch>'9'||ch<'0';ch=getchar()) if(ch=='-') w=-1;
	for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0';
	return x*w;
}
inline void print(__int128 x){
	if(x<0) {putchar('-');x=-x;}
	if(x>9) print(x/10);
	putchar(x%10+'0');
}
#define maxn 1000000
#define int long long
const int mod=1e9+7;
const double eps=1e-8;
#define	int_INF 0x3f3f3f3f
#define ll_INF 0x3f3f3f3f3f3f3f3f
struct Node{
	int r,c;
}node[maxn];
int dp[maxn];
signed main() {
	int T=read();
	while(T--) {
		int n=read();int k=read();
		int cnt=0;
		for(int i=1;i<=k;i++) {
			node[i].r=read();node[i].c=read();	
			if(node[i].r==node[i].c) {
				cnt++;
			}
		}
		dp[0]=1;dp[1]=1;
		for(int i=2;i<=n;i++) {
			dp[i]=(dp[i-1]+(i-1)*2%mod*dp[i-2]%mod)%mod;dp[i]%=mod;
		}
		if(n-cnt-2*(k-cnt)==0) {//可以不特判,我就是这个位置特判为0,然后卡了一整场
			cout<<1<<endl;
		}
		else {
			cout<<dp[n-cnt-2*(k-cnt)]<<endl;		
		}
	}
	return 0;
}

D. A BIT of an Inequality

我最喜欢位运算啦
考虑化简一下题目给的优美式子,即 f ( x , z ) f(x,z) f(x,z)^ y > f ( x , z ) y>f(x,z) y>f(x,z)的点对个数.那么显然区间异或和,我们可以使用前缀异或和快速求出.并且这种点对问题,经典的 t r i c k trick trick就是枚举中间数.所以我们考虑枚举 y y y.

考虑对于 y y y,它的贡献是什么.什么时候我们的 x x x,异或一下 y y y自己会变大.考虑拆位考虑,我们会发现 y y y当前位如果是0,对于x来说并不会产生影响.而如果 y y y当前为位1,是会产生影响的.那么我们考虑从高位往低位进行考虑,设 i i i y y y二进制位高位第一个 1 1 1的位置.那么我们此时的贡献就是当前位为 0 0 0的区间异或和的个数(这个我们之后再处理).此时会出现一个错误想法:我们会想到y后面还有假设还有一个位置j为1,我们考虑继续计算它的贡献,但是此时我们又会发现i和j的贡献可能会发生重复(我当时就被这个错误想法给卡了好久,当时感觉要容斥了).但是事实上上述的想法是不需要考虑的,因为假设我们会枚举到后续的j位置,因为我们还是从高位往低位枚举的,所以需要我们的前缀不小才行.但是我们的i位置已经是1了,此时区间异或和如果是1,那么此时从1->0,已经是前缀小了,就不需要继续考虑后续位置了.而如果区间异或和当前位是0,我们此时已经是前缀大了,也不需要考虑后续位置.所以我们只要考虑第一个1即可.

那么我们现在来考虑如何解决区间异或和贡献问题.可以考虑使用前缀和/后缀和解决.可以使用 l c n t [ i ] [ j ] lcnt[i][j] lcnt[i][j]来记录前缀j位为1的数的个数.使用 r c n t [ i ] [ j ] rcnt[i][j] rcnt[i][j]来记录后缀j位为1的个数.那么对于y来说,贡献就是前缀1的个数乘上后缀1的个数加上前缀0的个数乘上后缀0的个数.唯一需要注意的是因为是区间异或和,我们要包括住y这个位置,所以lcnt需要是y-1的前缀和,而rcnt是y之后的后缀和,并且还需要考虑0的位置

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define root 1,n,1
#define ls (rt<<1)
#define rs (rt<<1|1)
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
inline ll read() {
	ll x=0,w=1;char ch=getchar();
	for(;ch>'9'||ch<'0';ch=getchar()) if(ch=='-') w=-1;
	for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0';
	return x*w;
}
inline void print(__int128 x){
	if(x<0) {putchar('-');x=-x;}
	if(x>9) print(x/10);
	putchar(x%10+'0');
}
#define maxn 100010
#define int long long
const double eps=1e-8;
#define	int_INF 0x3f3f3f3f
#define ll_INF 0x3f3f3f3f3f3f3f3f
int a[maxn];int sum[maxn];int lcnt[maxn][31];
int rcnt[maxn][31];
signed main() {
	int T=read();
	while(T--) {
		int n=read();
		for(int i=1;i<=n;i++) {
			a[i]=read();
			sum[i]=sum[i-1]^a[i];
		}
		for(int i=0;i<=n;i++) {
			int temp=sum[i];
			int state=0;
			for(int j=0;j<=30;j++) {
				if(i-1>=0) {
					lcnt[i][j]=lcnt[i-1][j];
				}
				if(temp&(1<<j)) {
					lcnt[i][j]+=1;
				}
			}
		}
		for(int i=n;i>=0;i--) {
			int temp=sum[i];
			int state=0;
			for(int j=0;j<=30;j++) {
				rcnt[i][j]=rcnt[i+1][j];
				if(temp&(1<<j)) {
					rcnt[i][j]+=1;
				}
			}
		}
		int ans=0;
		for(int i=1;i<=n;i++) {
			for(int j=30;j>=0;j--) {
				if(a[i]&(1<<j)) {
					ans+=lcnt[i-1][j]*rcnt[i][j];
					ans+=(i-lcnt[i-1][j])*(n-i+1-rcnt[i][j]);
					break;
				}
			}
		}
		cout<<ans<<endl;
		//clear
		for(int i=0;i<=n+1;i++) {
			for(int j=0;j<=30;j++) {
				lcnt[i][j]=rcnt[i][j]=0;
			}
		}
	}
	
	
	
	
	return 0;
}

E. Carousel of Combinations

数数题.是 w i l s o n wilson wilson定理的应用.
对于 w i l s o n wilson wilson定理,请看这里,本人觉得讲的挺好,证明也很清晰严谨.
这道题,我刚开始没看到模数是会变的…然后自己搞出了 ( ∑ ∑ c ( i , j ) ) m o d ( 1 e 9 + 7 ) (\sum\sum c(i,j))mod(1e9+7) (∑∑c(i,j))mod(1e9+7)的解法(可以在 n l o g n nlogn nlogn时间内求出一个 n n n的答案).然后开敲.然后就发现有T个n,然后发现解决不了,然后就自闭了,然后就发现模数是 j j j.当然有兴趣的也可以想想这个"错题",我感觉也挺有意思(感觉比原题更加的数数题).

因为模数为 j j j,所以基本上我们是没办法枚举顺序了,留给我们的似乎只有化简原本的式子.先考虑计算一下 c ( i , j ) c(i,j) c(i,j),为了不和组合数冲突,我们将其换成 f ( i , j ) f(i,j) f(i,j),下文的C为组合数,运用一点点的组合数知识,我们不难发现 f ( i , j ) = C ( i , j ) ∗ j ! j f(i,j)=\frac{C(i,j)*j!}{j} f(i,j)=jC(i,j)j!.

简单解释一下为什么是这个式子,这是一个循环排列的问题.考虑 P ( i ) P(i) P(i)为直线排列的个数, Q ( i ) Q(i) Q(i)为圆排列的个数,那么对于每一个圆排列,我们会发现他都对应着 i i i个直线排列(考虑把每一个数字作为开头).所以 P ( i ) = Q ( i ) ∗ i P(i)=Q(i)*i P(i)=Q(i)i.所以 Q ( i ) = P ( i ) i Q(i)=\frac{P(i)}{i} Q(i)=iP(i).(上述证明其实不是很严谨,还需要证明不同的圆排列生成的直线排列都各不相同,但是这个过于显然且不易叙述,所以此处就不在赘述了,留给读者(可以考虑一个数字的相邻数不同从而来证明)).

所以此时我们的问题变成了 ∑ ∑ C ( i , j ) ∗ ( j − 1 ) ! \sum \sum C(i,j)*(j-1)! ∑∑C(i,j)(j1)!,稍微化简一下, ∑ ∑ i ! j ∗ ( i − j ) ! m o d    j \sum \sum \frac{i!}{j*(i-j)!}mod\;j ∑∑j(ij)!i!modj.此时我们需要亿点点的观察能力.我们会发现其实是一个 i i i j j j次下降次幂的形式除上一个 j j j.我们需要发现 i i i j j j次下降次幂其实是恰好包含了0~j-1所有mod j的余数的数字.也就是说其中一定存在一个数是 j j j的倍数,设其为 k k k,那么显然的,我们就可以将我们的 j j j除掉,剩下来 k j \frac{k}{j} jk,剩余的数字显然模 j j j之后的贡献为 ( j − 1 ) ! (j-1)! (j1)!.我们现在考虑 k j \frac{k}{j} jk等于多少,我们会发现他就等于 ⌊ i j ⌋ \lfloor \frac{i}{j} \rfloor ji(这个可以通过分类 i i i是否是j的剩余系的开头来证明(也就是分类 i i i j j j之后的余数是否是0),鉴于篇幅原因,此处就不在赘述了).那么此时我们的贡献就变成了: ∑ ∑ ⌊ i j ⌋ ( j − 1 ) !    m o d    j \sum \sum \lfloor \frac{i}{j} \rfloor(j-1)!\;mod\;j ∑∑ji(j1)!modj.此时还不够,因为模数的原因,阶乘没办法预处理.但是此时我们就可以祭出我们的 w i l s o n wilson wilson定理了. w i l s o n wilson wilson定理告诉我们对于任意的质数 p p p,都满足 ( p − 1 ) ! ≡ p − 1    m o d    p (p-1)!\equiv p-1\;mod\;p (p1)!p1modp,对于非质数,除4以外,都是0,特别的,4的时候答案是2.所以我们可以得出 ∑ ∑ ⌊ i j ⌋ ( j − 1 )    m o d    j [ j ∈ p r i m e ] \sum \sum \lfloor \frac{i}{j} \rfloor (j-1)\;mod\;j[j\in prime] ∑∑ji(j1)modj[jprime] ∑ ⌊ i 4 ⌋ ∗ 2    m o d    4 \sum \lfloor \frac{i}{4} \rfloor *2\;mod\;4 4i2mod4

这是一个经典的 t r i c k trick trick.我们设 a [ i ] a[i] a[i] ∑ j = 1 i f ( i , j ) \sum_{j=1}^i f(i,j) j=1if(i,j)的总贡献.考虑先求出 a [ i ] a[i] a[i],我们可以枚举 g = ⌊ i j ⌋ g=\lfloor \frac{i}{j} \rfloor g=ji,考虑高斯函数的经典展开式 g ≤ i j < g + 1 g\leq\frac{i}{j}< g+1 gji<g+1,我们可以得到 g ∗ j ≤ i < ( g + 1 ) ∗ j g*j\leq i <(g+1)*j gji<(g+1)j,并且我们会发现此时的贡献就是 g ∗ ( j − 1 ) g*(j-1) g(j1),我们再枚举 j j j,这样我们的贡献就求出来了.使用差分数组维护一下区间修改即可.但是别忘了 4 4 4的情况,我们需要单独将其补上.还有就是此时我们只是求出了 a [ i ] a[i] a[i],题目要求我们的是前缀和,所以我们再进行一次前缀和即可解决.

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define root 1,n,1
#define ls (rt<<1)
#define rs (rt<<1|1)
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
inline ll read() {
	ll x=0,w=1;char ch=getchar();
	for(;ch>'9'||ch<'0';ch=getchar()) if(ch=='-') w=-1;
	for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0';
	return x*w;
}
inline void print(__int128 x){
	if(x<0) {putchar('-');x=-x;}
	if(x>9) print(x/10);
	putchar(x%10+'0');
}
#define maxn 1000010
#define int long long
const int mod=1e9+7;
const double eps=1e-8;
#define	int_INF 0x3f3f3f3f
#define ll_INF 0x3f3f3f3f3f3f3f3f
int vis[maxn],prime[maxn],cnt=0,mp[maxn];
void init(int limit=1e6) {
	for(int i=2;i<=limit;i++) {
		if(!vis[i]) prime[++cnt]=i,mp[i]=1;
		for(int j=1;j<=cnt;j++) {
			if(i*prime[j]>limit) break;
			vis[i*prime[j]]=1;
			if(i%prime[j]==0) break;
		}
	}
}
int sum[maxn];
signed main() {
	init();
	for(int j=2;j<=1e6;j++) {
		if(mp[j]==0) continue;
		for(int k=1;k*j<=1e6;k++) {
			int l=k*j,r=min((int)1e6,(k+1)*j-1);
			sum[l]+=(j-1)*k%j;sum[r+1]-=(j-1)*k%j;
		}
	}
	for(int i=1;i<=1e6;i++) {
		sum[i]+=sum[i-1];sum[i]%=mod;
	}
	for(int i=1;i<=1e6;i++) {
		sum[i]+=2*(i/4)%4;
		sum[i]%=mod;
	}
	for(int i=1;i<=1e6;i++) {
		sum[i]+=sum[i-1];sum[i]%=mod;
	}
	int T=read();
	while(T--) {
		int n=read();
		cout<<sum[n]<<endl;
	}
	return 0;
}
  • 18
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值