CF选做

CF1895C

方法:数位dp
使用f[i][j]表示长串+短串的情况下,长串分割成两部分,且
数位数差位i,数位和差为j的方案数。
通过预处理出f数组得到答案

#include <stdio.h>
#include <string.h>
#include <math.h>
#define N 400000
#define ll long long
//typedef long long ll

ll f[200][200],g[200][200];
ll a[N],b[N],c[N];
ll n,ans;

int main()
{
	scanf("%lld",&n);
	for(int i=1;i<=n;++i)
	{
		scanf("%lld",&a[i]);
		int x=a[i],y=1;
		while(x) {++b[i];c[i]+=x%10;x/=10;y*=10;}
		x=a[i]*10;int now=c[i];
		for(int j=0;j<=b[i];++j)
		{
			now=now-x%10*2;
			if(b[i]-j*2<=0||now<=0) break;
			f[b[i]-j*2][now]++;
			x/=10;
		}//the first is no shorter than the latter
		y/=10;now=c[i];
		for(int j=1;j<=b[i];++j)
		{
			now=now-a[i]/y%10*2;y/=10;
			if(b[i]-j*2<=0||now<=0) break;
			g[b[i]-j*2][now]++;
		}//the latter is shorter than the first one
	}
	for(int i=1;i<=n;++i)
	{
		ans+=f[b[i]][c[i]]+g[b[i]][c[i]];
	}
	printf("%lld\n",ans);
	return 0;
}

CF1895D

数位贪心,首先通过异或的性质得到,设c[i]=a[1]...a[i]则b[i]=b[0]^c[i]
然后只需要找到合适的b[0]即可。
因为题目保证有解,所以c[i]不可能有两个相同的元素(否则就无解),这样排除了重复的情况就只剩下b[0]^c[i]>=n的情况了
之后考虑每一位,对每一位进行贪心,统计c数组所有数的第i位,如果1比0多就让b[0]=1,这样可以最小化b数组所有数的总和,从而最小化其最大值。

#include<iostream>
#include<cstdio>
#include<cmath>

using namespace std;

const int N = 400000;
int n,m,ans;
int a[N],b[N],c[N];

int main()
{
	scanf("%d",&n);
	for(int i=1;i<n;++i)
	{
		scanf("%d",&a[i]);
		c[i]=c[i-1]^a[i];
	}
	for(int i=0;i<=31;++i)
	{
		int tmp=1<<i,num=0;
		for(int j=1;j<n;++j)
		{
			if(c[j]&tmp) ++num;
		}
		if(num>n/2)
			ans|=tmp;
	}
	for(int i=0;i<n;++i)
//		printf("%d ",c[i]);
		printf("%d ",ans^c[i]);
	return 0;
}

CF1899D

只想到了化成\(a/2^a=b/2^b\)的形式,没有进一步分析二者图像emm

思路1 分析图象,发现只有x=1或x=2时等式成立
思路2 设b>a,则可设\(b=a*2^k\),从而发现b不能很大,可能还是这类套路掌握得不够熟练。

map的使用

map<double,string> ha;//定义map

ha x;x.first=1.2;x.second="abc";//单点调用

ha[3.4]++;//用键调用值

for(auto i : ha){
printf("%lf\n%s\n",i.first,i.second);
}//遍历map

#include<cstdio>
#include<iostream>
#include<cmath>
#include<map>

#define eps 0.00006
#define ll long long
using namespace std;

map<int,ll>m;

int main()
{
	int t;scanf("%d",&t);while(t--){
		ll n,ans=0;scanf("%lld",&n);
		/**for(int i=1;i<=n;++i)
		{
			double x;scanf("%lf",&x);
			if(x==1) x=2;
			a[i]=x*log(2)-log(x*1.0); //log() means ln,log_e
			//Dialog:double is not big enough

			/*
			for(int j=1;j<i;++j)
				if(abs(a[i]-a[j])<eps)
					++ans;
			//TLE,O(n^2) is too slow
			Sol1:Sort,then scan the same numbers
			Sol2:Use map
			* /
			ans+=m[a[i]*1000000];
			m[a[i]*1000000]++;
		}*/
		for(int i=1;i<=n;++i)
		{
			int x;scanf("%d",&x);
			if(x==1) x=2;
			ans+=m[x];
			m[x]++;
		}
		printf("%lld\n",ans);
		m.clear();
	}
	return 0;
}

CF1896D

非常好一道题,使我放弃python

python中没有C++中set和map的内置红黑树,而这道题可能需要用到

首先发现结论:如果存在区间和 s 可以取到,那么 s-2 就一定可以取到,因此与数列总和 sum 奇偶性相同的 s 肯定能取到。

与 sum 奇偶性不同的 s 只需要判断离两端最近的 1 有多近就行。

(证明:分情况讨论:取到s时两端有至少一个 2 或者 有两个 1 ,从而得到 s-2 能取到)

然而维护离两端最近的 1 不能用散列表,否则每次查询最坏复杂度为 O(n) ,总复杂度O(Tmn)会在第9个点上t掉,需要一种数据结构支持单点插入和单点删除并维护整个数列的最值——红黑树

set的用法

//https://zhuanlan.zhihu.com/p/682656691

#include<cstdio>
#include<set>
using namespace std;

set<int>b;

int main()
{
	int n;scanf("%d",&n);

	//set的基本功能
	b.clear();//清空
	for(int i=1;i<=n;++i)
	{
		int x;scanf("%d",&x);
		b.insert(x);//插入
	}
	printf("%d %d %d\n",*b.begin(),*b.rbegin(),*b.size());
	//最小值b.begin(),最大值b,rbegin()
	//而b.end()返回的是最大值后面的迭代器,b.rend是最小值后面的迭代器

	//set查找:
	auto it=b.find(3);
	if (it!=b.end()){
	    cout<<"Found: "<<*it<<"\n";
	}else{
	    cout<<"Not found.\n";
	}//找到返回地址,没找到返回b.end()

	//set的遍历:
	for (auto it = b.begin(); it != b.end(); ++it) {
	    cout << *it << " ";
	}
	return 0;
}
#include<cstdio>
#include<iostream>
#include<cmath>
#include<set>
using namespace std;

int n,m,sum,a[200000];
set<int> b;

int mini()
{
	if(b.empty()) return n;
	return min(*b.begin()-1,n-*b.rbegin());
}

void judge(int s)
{
	if(s%2==sum%2&&sum>=s)
	{
		printf("YES\n");
		return;
	}
	if(sum-2*mini()-1>=s)
	{
		printf("YES\n");
		return;
	}
	printf("NO\n");
}

void doit()
{
	sum=0;b.clear();
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;++i)
	{
		scanf("%d",&a[i]);
		sum+=a[i];
		if(a[i]==1)
			b.insert(i);
	}
	for(int i=1;i<=m;++i)
	{
		int op;scanf("%d",&op);
		if(op==1)
		{
			int s;scanf("%d",&s);
			judge(s);
		}
		else
		{
			int u,v;scanf("%d%d",&u,&v);
			if(a[u]==1&&v==2)
			{
				a[u]=2;
				b.erase(u);
				++sum;
			}
			else if(a[u]==2&&v==1)
			{
				a[u]=1;
				b.insert(u);
				--sum;
			}
		}
	}
}

int main()
{
	int t;scanf("%d",&t);
	while(t--){
		doit();
	}
	return 0;
}

CF1903C

维护后缀和并在后缀和上找规律,观察到如果第i+1个数的后缀和非负,那么在第i个和第i+1个之间分组一定不会使总和变小。

注意先观察后写代码,不要在细节上浪费时间

注意开ll

#include<cstdio>
#include<iostream>
#define ll long long

using namespace std;

int n;
ll a[200000],sub[200000];

int main()
{
	int t;scanf("%d",&t);while(t--){
		scanf("%d",&n);
		for(int i=1;i<=n;++i) scanf("%lld",&a[i]);
		sub[n]=a[n];
		for(int i=n-1;i>=1;--i) sub[i]=sub[i+1]+a[i];
		ll num=0,tmp=0,ans=0;
		for(int i=1;i<=n;++i)
		{
			tmp+=a[i];
			if(sub[i+1]>=0)
			{
				++num;
				ans+=tmp*num;
				tmp=0;
				continue;
			}
		}
		if(num) ans+=tmp*num;
		else ans+=tmp;
		printf("%lld\n",ans);
	}
}

CF1948D

经典前缀后缀优化,通过预处理任意两个后缀的最长公共前缀来 \(O(1)\) 判断子串 \([l,r]\) 是否符合要求。很经典也很巧妙

#include<cstdio>
#include<iostream>
#include<cstring>

#define ll long long
using namespace std;

char a[10000];
int f[6000][6000];

int main()
{
    int t;scanf("%d\n",&t);while(t--){
    	scanf("%s",a);int n=strlen(a),ans=0;
    	for(int i=n-1;i>=0;--i)
    		for(int j=n-1;j>=0;--j)
    			if(a[i]==a[j]||a[i]=='?'||a[j]=='?')
    				f[i][j]=f[i+1][j+1]+1;
    	for(int l=0;l<n;++l){
    		for(int r=l;n-r>=r-l+2;++r){
				if(f[l][r+1]==r-l+1)
					ans=max(ans,2*(r-l+1));
			}
		}
		printf("%d\n",ans);
    	for(int i=n-1;i>=0;--i)
    		for(int j=n-1;j>=0;--j)
    			f[i][j]=0;
	}
	return 0;
}

CF1907D

二分答案,注意二分答案的边界一定是[0,inf] ,而不是区间间距的边界!


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

const int N=400000;
const int inf=1000000000;
int l[N],r[N];
int n;

int f(int k){
	int nowl=0,nowr=0;
	for(int i=1;i<=n;++i){
		if(l[i]-nowr>k||nowl-r[i]>k) return 0;
		nowl=max(l[i],nowl-k);
		nowr=min(r[i],nowr+k);
	}
	return 1;
}

int main()
{
    int t;scanf("%d\n",&t);while(t--){
    	scanf("%d",&n);
    	for(int i=1;i<=n;++i) scanf("%d%d",&l[i],&r[i]);
		int ll=0,rr=inf;
		while(ll<rr){
			int mid=(ll+rr)/2;
			if(f(mid)) rr=mid;
			else ll=mid+1;
		}
		printf("%d\n",ll);
	}
	return 0;
}

CF1944B

有时候感觉写的很不自信的大模拟,捋顺一点也就能写出来了

#include<cstdio>
#include<iostream>
#include<vector>
#include<map>
#define ll long long
using namespace std;

const int N = 100000;
int a[N],b[N];
map<int,int> m;
vector<int> v,va,vb;

int main()
{
	int T;cin>>T;while(T--){
		int n,k,cnt=0,cnta=0,cntb=0;cin>>n>>k;m.clear();v.clear();va.clear();vb.clear();
		for(int i=1;i<=n;++i) cin>>a[i];
		for(int i=1;i<=n;++i) cin>>b[i];
		for(int i=1;i<=n;++i) m[a[i]]++;
		for(int i=1;i<=n;++i) m[b[i]]--;
		for(int i=1;i<=n;++i){
			if(m[a[i]]==2) {
				va.push_back(a[i]);++cnta;m[a[i]]=N;
			}
			if(m[b[i]]==-2){
				vb.push_back(b[i]);++cntb;m[b[i]]=N;
			}
			if(m[a[i]]==0){
				++cnt;v.push_back(a[i]);m[a[i]]=N;
			}
		}
		for(int i=0;i<cnta&&i<k;++i){
			cout<<va[i]<<' '<<va[i]<<' ';
		}
		for(int i=0;i<(k-cnta)*2;i+=2){
			cout<<v[i]<<' '<<v[i+1]<<' ';
		}cout<<endl;
		for(int i=0;i<cntb&&i<k;++i){
			cout<<vb[i]<<' '<<vb[i]<<' ';
		}
		for(int i=0;i<(k-cnta)*2;i+=2){
			cout<<v[i]<<' '<<v[i+1]<<' ';
		}cout<<endl;
	}
	return 0;
}

CF1944C

看了hint才做出来

Alice can adapt to Bob's strategy. Try to keep that in mind.

每个数分出现0次、1次和多次三种情况

#include<cstdio>
#include<iostream>
#include<map>
#include<algorithm>
#define ll long long
using namespace std;

const int N = 400000;
int a[N];
map<int,int> m;
int main()
{
	int T;cin>>T;while(T--){
		int n,b=0;cin>>n;m.clear();
		for(int i=1;i<=n;++i) {
			int x;cin>>x;
			m[x]++;
		}
		for(int i=0;;++i){
			if(!m[i]){
				cout<<i<<endl;
				break;
			}
			if(m[i]==1){
				if(b){
					cout<<i<<endl;
					break;
				}
				b=1;
			}
		}
	}
	return 0;
}

CF1955D

灵活运用map或set计算滑动窗口中每个值的贡献,居然能做到\(O(nlogn)\),震惊(

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<map>

#define ll long long
using namespace std;

const int N=300000;
int a[N],b[N];
map<int,int>ma;

int main(){
	int T;cin>>T;while(T--){
		int n,m,k,num=0,ans=0;cin>>n>>m>>k;ma.clear();
		for(int i=1;i<=n;++i) cin>>a[i];
		for(int i=1;i<=m;++i) {cin>>b[i];ma[b[i]]+=1;}
		for(int i=1;i<=m;++i){
			if(ma.find(a[i])!=ma.end()){
				--ma[a[i]];
				if(ma[a[i]]>=0) ++num;
			}
		}if(num>=k) ++ans;
		for(int i=m+1;i<=n;++i){
			if(ma.find(a[i-m])!=ma.end()){
				++ma[a[i-m]];
				if(ma[a[i-m]]>0) --num;
			}
			if(ma.find(a[i])!=ma.end()){
				--ma[a[i]];
				if(ma[a[i]]>=0) ++num;
			}
			if(num>=k) ++ans;
		}
		cout<<ans<<endl;
	}
	return 0;
}

CF1955E

不知道为什么不能线性做,这不核理

不能二分,因为如果n符合题意n-1不一定符合

正解就是在暴力判断上加一个优化,通过异或差分实现\(O(1)\)的修改,总复杂度\(O(n^2)\)

#include<cstdio>
#include<iostream>
#include<algorithm>
#define ll long long
using namespace std;

int n;string a;
int tmp[10000];

int f(int x){
	int now=a[0]-'0';tmp[0]=a[0]-'0';
	for(int i=1;i<n;++i)
		tmp[i]=(a[i-1]-'0')^(a[i]-'0');
	for(int i=0;i<n;now^=tmp[++i]){
		if(now==1) continue;
		if(i+x>n) return 0;
		tmp[i]^=1;now^=1;	if(i+x<n) tmp[i+x]^=1;
	}
	return 1;
}

int main(){
	int T;cin>>T;while(T--){
		cin>>n>>a;
		for(int l=n;l>=1;--l)
			if(f(l)){
				cout<<l<<endl;
				break;
			}
	}
	return 0;
}

CF1957C

首先发现已经占有的格子的坐标与最终答案无关,可以把它转化成m*m的方格进行分析,然后发现如果放在对角线上的格子就减少一行一列,其他减少两行两列。设有n-i个对角线格子放上了棋子(方法数为\(C_n^{n-i}\)),那么剩下i个格子(i必须是偶数)就要放在其他的格子上。每次挑选两列消失\(2*C_n^2=n(n-1)\),n/2次共\(n!\)种选法,再除以\((n/2)!\)去重即可得到答案。

\[ans=\sum_{i=0}^n C_n^{n-i}\frac{i!}{(\frac{i}{2})!}(i为偶数) \]

需要预处理阶乘

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int mod=1000000007;

int f[400000];

void init(){
	f[0]=1;
	for(int i=1;i<=300000;++i){
		f[i]=f[i-1]*i%mod;
	}
}

int re(int x){
	int tmp=mod-2,ans=1;
	while(tmp){
		if(tmp&1) ans=ans*x%mod;
		x=x*x%mod;tmp/=2;
	}
	return ans;
}

int c(int n,int m){
	int num=1,den=1;
	// for(int i=n-m+1;i<=n;++i) num=(num*i)%mod;
	// for(int i=1;i<=m;++i) den=(den*i)%mod;
	num=f[n];den=f[m]*f[n-m]%mod;
	return (num*re(den))%mod;
}

int g(int x){
	// if(!x) return 1;
	int ans=1,den=1;
	// for(int i=x;i>1;--i) ans=ans*i%mod;
	// for(int i=x/2;i>1;--i) den=den*i%mod;
	ans=f[x];den=f[x/2];
	return ans*re(den)%mod;
}

signed main(){
	init();
	int T;cin>>T;while(T--){
		int n,k,tmp=0,ans=0;cin>>n>>k;
		while(k--){
			int A,B;cin>>A>>B;
			if(A==B) tmp+=1;else tmp+=2;
		}n-=tmp;
		for(int i=0;i<=n;i+=2){
			ans=(ans+c(n,i)*g(i))%mod;
		}
		cout<<ans<<endl;
	}
	return 0;
}
posted @ 2024-01-12 14:42  hcx1999  阅读(14)  评论(0编辑  收藏  举报