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)!\)去重即可得到答案。
需要预处理阶乘
#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;
}