2024年6月杂题乱写

6.5

P3214 [HNOI2011] 卡农

fi 表示选了 m 个集合的答案,简单观察发现,只要确定了 m1 个集合,最后一个集合就是确定的,不是偶数次数的出现,偶数次数的不出现,选 m 个集合有 C2n1m1 种方案,考虑下面两种不合法的情况。

  • m1 个集合已经合法,最后一个集合为空集。
  • 最后要选的集合在前面 m1 个集合出现过。

答案就是总数减去这两种情况,第一种情况显然为 fi1,第二种情况其实就是有 i2 个集合已经合法了,数量为 fi2,但是因为最后的一个集合(相同的两个)不确定,所以第二种情况实际上有 fi2[2n1(i2)],直接减去就可以,这时因为我们每一种都算了 m 遍,所以答案要除以 m

fi=C2n1i1fi1fi2(2ni+1)i

直接递推即可。

点击查看代码
#include<bits/stdc++.h>
#define int long long
inline int read(){
char ch=getchar();int x=0,f=1;
for(;ch<'0'||ch>'9';ch=getchar())if(ch=='-')f=-1;
for(;ch>='0'&&ch<='9';ch=getchar())x=(x<<3)+(x<<1)+(ch^48);
return x*f;
}
const int N=1e6+10,mod=1e8+7;
inline int mo(int x){return x<0?(x%mod+mod)%mod:(x>=mod?x%mod:x);}
inline int qpow(int a,int b){
int res=1;
for(;b;b>>=1,a=mo(a*a))if(b&1)res=mo(res*a);
return res;
}
int n,m,sum,inv[N],C,f[N];
signed main(){
// freopen("in.in","r",stdin),freopen("out.out","w",stdout);
std::ios::sync_with_stdio(false);std::cin.tie(0),std::cout.tie(0);
n=read(),m=read();sum=mo(qpow(2,n)-1);
if(m>sum){std::cout<<0<<'\n';exit(0);}
inv[1]=1,f[1]=f[2]=0;f[0]=1;
for(int i=2;i<=m;++i)inv[i]=mo((mod-mod/i)*inv[mod%i]);
C=mo(mo(sum*(sum-1))*inv[2]);
for(int i=3;i<=m;++i){
f[i]=mo(mo((C-f[i-1]-mo(f[i-2]*mo(sum-i+2))))*inv[i]);
C=mo(mo(C*(sum-i+1))*inv[i]);
}
std::cout<<f[m]<<'\n';
}

6.9

困困困,头疼疼疼疼疼,给大家来场 ABC 速通。

  • A 直接扫
  • B 直接扫
  • C 递归周围,然后中间填白色
  • D 设 len 为数字 n 十进制下的长度,Vn=ni=0n110ilen,后面直接等比数列求和 ans=n10nlen110len1
  • E 就是求有向图每个点能到达的点(包括自己)的和,反向建边后直接缩点拓扑排序跑 DP 即可。
  • F 一眼线段树,赛时傻逼没推下式子锅了,(a+x)(b+y)=ab+xb+ya+xy,记两个 tag 即可。
  • G 高级东西,不会。

6.15 ABC358 速通

D 直接二分找,然后删。
E 就是求多重集排列数,直接设 fi,j 表示考虑前 i 个字母,长度为 j 时的方案数,枚举新加入多少个元素,有 fi,j=k=max(0,jci)jfi1,jCjk,最后 ans=i=1nf26,i
F 随便找,然后比较傻逼,没意思。
G 直接设状态 ft,i,j 表示 t 时刻在 (i,j) 的最大价值,转移就直接从 5 个位置转移,发现 k 很大,但是当 knm 时就没啥意义了,因为此时的最优解一定不会再从其他点转移了,所以考虑 knm 时的答案,最后加上剩下的次数乘上价值找最大就行。

6.17

感觉这种博客还是要写下去,

CF1808D Petya, Petya, Petr, and Palindromes

比较水的题。首先直接不太好想,正难则反,考虑将所有不需要改变的点对处理出来,拿总数一减就是答案。
发现合法数就是两个位置的数相同,所以就变成了,对于每一个数,查询指定区间内与它相等的数的个数。
因为需要满足回文的对称性,所以偶数位置只会和偶数位置匹配,奇数位置只会和奇数位置匹配,此时其实可以直接无脑上数据结构了,时间复杂度 O(nlogn)
不考虑数据结构,这里有两种处理方法。

  • 开个桶记录一下每个数的出现次数,类似莫队的思想,两个指针记录一下桶的范围,每次查到 ai 时,求出与 ai 的对应位置范围,然后移动指针,如果位置 ji 奇偶性相同,就产生 1 的贡献,奇数偶数各查一次。
  • 开桶记录每个数出现的奇数位置和偶数位置,对于每个数,双指针不断向右扩展,直到不合法时记录一下两个指针之间的数的个数,也是奇数偶数都查一次。

这两种做法的时间复杂度都是 O(n),做法一因为区间单调,所以保证了复杂度,做法二显然是线性,这里给出做法一的代码。

#include<bits/stdc++.h>
#define int long long
typedef long long ll;
typedef unsigned long long ull;
inline int read(){char ch=getchar();int x=0,f=1;for(;ch<'0'||ch>'9';ch=getchar())if(ch=='-')f=-1;for(;ch>='0'&&ch<='9';ch=getchar())x=(x<<3)+(x<<1)+(ch^48);return x*f;}
const int N=2e5+10;
int a[N],n,k,q[N],mid,l1,r1,ans;
inline void work(int pd){
l1=1,r1=0;
memset(q,0,sizeof(q));
for(int i=mid+2+pd;i<=n;i+=2){
int L=std::max(1ll,i-k+1),R=std::min(n,i+mid-1);
int l=(L+L+k-1)-i,r=(R+(R-k+1))-i;
while(r1<r){if((r1&1)!=(i&1))q[a[++r1]]++;else ++r1;}
while(r1>r){if((r1&1)==(i&1))q[a[r1--]]--;else --r1;}
while(l1<l){if((l1&1)==(i&1))q[a[l1++]]--;else ++l1;}
while(l1>l){if((l1&1)!=(i&1))q[a[--l1]]++;else --l1;}
ans+=q[a[i]];
}
}
signed main(){
// freopen("in.in","r",stdin);freopen("out.out","w",stdout);
std::ios::sync_with_stdio(false);std::cin.tie(0);std::cout.tie(0);
n=read();k=read();
for(int i=1;i<=n;++i)a[i]=read();
mid=k/2;int pd=mid&1;
work(pd),work(pd^1);
std::cout<<(n-k+1)*(k/2)-ans<<'\n';
}

CF1526D Kill Anton

感觉是比较神的构造,直接构造答案,比较困难,思考 b 串到 a 的最大代价,因为交换相邻的元素,可以想到逆序对,给序列 a 依次标号,这样由 ba 的交换次数就是 b 的逆序对数量。题目就转化为了求逆序对数量最多的 b 串,但是同种字母之间是没有区别的,所以在最优策略下,同种字母直接是不会进行交换的,不属于逆序对,所以不能简单的将 a 串翻转。
有引理:一定存在所有同类字母相邻的最优答案。
感性理解:因为字母之间只会不同类交换,所以考虑把不同类的字母尽量放前面,同类在一起。
证明:

  • 在最优策略下,同类字母不会发生交换,所以对于任意排列,同类字母之间的标号都是递增的。
  • 对于同类字母 aiaj 之间的所有相邻异类字母 akaq 来说,如果 ak 这种字母中 w 个字母与前面的 ai 这种字母共构成了 v1 个逆序对,s 个与后面的 ai 这种字母共构成了 v2 个逆序对,有 v1(ki)×w,v2(jq)×s。如果将所有的 ak 交换到前面,此时新增了 [(ki)×sv1] 个逆序对,如果将他们交换到后面,此时新增了 [(jq)×wv2] 个逆序对。
  • (ki)×sv10,即 (ki)×s(ki)×w 时,有 sw,则 v2(jq)×s(jq)×w,即 (jq)×wv20。同理,如果 (jq)×wv20,则 (ki)×sv10
  • 所以对于任何一种情况,我们总能使得同类字母相邻且逆序对数量单调不减,即一定存在所有同类字母相邻的最优答案。

由于不确定字母之间的顺序,我们可以直接枚举排列,然后每次用树状数组查询逆序对数量(当然也可以 O(n) 直接计算交换次数),取最大即可,时间复杂度 O(24nlogn)

#include<bits/stdc++.h>
#define int long long
typedef long long ll;
typedef unsigned long long ull;
inline int read(){char ch=getchar();int x=0,f=1;for(;ch<'0'||ch>'9';ch=getchar())if(ch=='-')f=-1;for(;ch>='0'&&ch<='9';ch=getchar())x=(x<<3)+(x<<1)+(ch^48);return x*f;}
const int N=1e5+10;
int n,a[N],ans[N],_ans,t[N];
char s[N];
bool vis[N];
std::vector<int> v[5];
inline void add(int x){
for(;x<=n;x+=x&-x)t[x]+=1;
}
inline int query(int x){
int res=0;
for(int i=n;i;i-=i&-i)res+=t[i];
for(int i=x;i;i-=i&-i)res-=t[i];
return res;
}
inline void solve(){
for(int i=1;i<=n;++i)t[i]=0;
int res=0;
for(int i=1;i<=4;++i){
for(int x:v[a[i]]){
res+=query(x);
add(x);
}
}
if(_ans<=res){
for(int i=1;i<=4;++i)ans[i]=a[i];
_ans=res;
}
}
inline void dfs(int len,int x){
if(len)a[len]=x;
if(len==4){
solve();
return;
}
for(int i=1;i<=4;++i){
if(!vis[i]){
vis[i]=1;
dfs(len+1,i);
vis[i]=0;
}
}
}
signed main(){
// freopen("in.in","r",stdin);freopen("out.out","w",stdout);
std::ios::sync_with_stdio(false);std::cin.tie(0);std::cout.tie(0);
int t;std::cin>>t;
while(t--){
_ans=0;
for(int i=1;i<=4;++i)v[i].clear();
std::cin>>s+1;
n=strlen(s+1);
for(int i=1;i<=n;++i){
if(s[i]=='A')v[1].push_back(i);
if(s[i]=='N')v[2].push_back(i);
if(s[i]=='T')v[3].push_back(i);
if(s[i]=='O')v[4].push_back(i);
}
dfs(0,0);
for(int i=1;i<=4;++i){
for(int j=1;j<=v[ans[i]].size();++j){
if(ans[i]==1)std::cout<<'A';
if(ans[i]==2)std::cout<<'N';
if(ans[i]==3)std::cout<<'T';
if(ans[i]==4)std::cout<<'O';
}
}
std::cout<<'\n';
}
}

6.19

CF1978D Elections

感觉 C 比 D 难,为啥这场能掉分,为啥 C 对不出脑电波,感觉赛时死磕很不好。
有一点比较显然,如果一个人不能直接获胜的话,那么他前面的人都应该被撤下,这样才会改变这个人的票数,才有可能使他获胜。
接下来有两种情况:

  • 撤完前面的人后,他的票数大于等于剩下的人的最多票数的话,那么此时直接获胜。
  • 撤完前面的人后,他的票数小于剩下的人的最多票数的话,此时再把票数最多的人撤下,这样他就成了票数最多的人,获胜。

直接做前缀和和后缀最大值就行,时间复杂度 O(n),注意开 long long。

#include<bits/stdc++.h>
#define int long long
typedef long long ll;
typedef unsigned long long ull;
inline int read(){char ch=getchar();int x=0,f=1;for(;ch<'0'||ch>'9';ch=getchar())if(ch=='-')f=-1;for(;ch>='0'&&ch<='9';ch=getchar())x=(x<<3)+(x<<1)+(ch^48);return x*f;}
const int N=2e5+10;
int a[N],sum[N],max[N];
signed main(){
// freopen("in.in","r",stdin);freopen("out.out","w",stdout);
std::ios::sync_with_stdio(false);std::cin.tie(0);std::cout.tie(0);
int T=read();
while(T--){
int n=read(),c=read();
for(int i=1;i<=n;++i)a[i]=read();a[1]+=c;
for(int i=1;i<=n;++i)sum[i]=sum[i-1]+a[i];
max[n+1]=0;
for(int i=n;i;--i)max[i]=std::max(a[i],max[i+1]);
int st=0;
for(int i=1;i<=n;++i){
if(a[i]==max[1]&&st<max[1]){std::cout<<0<<' ';}
else{
int ans=i-1;
if(sum[i]<max[i+1]){ans++;}
std::cout<<ans<<' ';
}
st=std::max(st,a[i]);
}
std::cout<<'\n';
}
}

6.20

CF1736D Equal Binary Subsequences

感觉是对脑电波题,能对上黄,对不上紫。
首先对于 10 的个数是奇数的话一定不合法,特判掉即可。
经过一些数据的模拟,我们好像并不能找出完全不合法的情况,这时可以大胆猜测如果数量不是奇数的话一定有解。
考虑什么样的序列一定是合法的,不难发现,两位两位的进行分组,每组都一样的序列一定是合法的,如 11110011000000111100,对于答案 p,直接选奇数位就可以。
事实上,对于任意一个可能合法的序列,都能构造出一种方案,使得他与上面序列同构。
具体来说,我们扫描每一组,如果这两个数不一样,选出一个和上一个选择不一样的那一位(如果上次没选过就随便选),扫描完毕后,将选出的子序列右移一位。此时每组的两个数都相同,合法。
考虑为什么第一组的数也一定相同:因为 10 的个数为偶数,所以选出来的数一定有偶数个 01,并且他们是交错的,所以右移之后一定能使每一组的数都相同。

#include<bits/stdc++.h>
typedef long long ll;
typedef unsigned long long ull;
inline int read(){char ch=getchar();int x=0,f=1;for(;ch<'0'||ch>'9';ch=getchar())if(ch=='-')f=-1;for(;ch>='0'&&ch<='9';ch=getchar())x=(x<<3)+(x<<1)+(ch^48);return x*f;}
const int N=2e5+10;
char s[N];
signed main(){
// freopen("in.in","r",stdin);freopen("out.out","w",stdout);
std::ios::sync_with_stdio(false);std::cin.tie(0);std::cout.tie(0);
int T;std::cin>>T;
while(T--){
int n;std::cin>>n;n*=2;
std::cin>>s+1;
std::vector<int> v;
int _1=0,_0=0;
for(int i=1;i<=n;++i)_1+=s[i]=='1',_0+=s[i]=='0';
for(int i=1;i<=n;i+=2){
int x=i,y=i+1;
if(s[x]!=s[y]){
if(v.size()){
if(s[x]!=s[v[v.size()-1]])v.push_back(x);
else v.push_back(y);
}
else v.push_back(x);
}
}
if(_1&1){std::cout<<-1<<'\n';continue;}
std::cout<<v.size()<<' ';
for(int x:v)std::cout<<x<<' ';
std::cout<<'\n';
for(int i=1;i<=n;i+=2)std::cout<<i<<' ';std::cout<<'\n';
}
}

这题真的从头到尾都想假了,如果想的总有hack,及时换思路。

6.21

P9510 『STA - R3』高维立方体

更多关于斐波那契数列的知识
fi 表示斐波那契数列的第 i 项。

引理一:i=1nfi2=fnfn+1
证明:显然 i=1nfi2=fnfn+1 对于 n=3 成立,假设该引理对于 n=k 成立,对于 n=k+1,有

i=1k+1fi2=i=1kfi2+fk+12=fkfk+1+fk+12=fk+1fk+2

i=1nfi2=fnfn+1,证毕。

简单整理下所求式子,得 i=1n(fifi1fi2+fi3)+fnfn+1
对于 i=1nfifi1fi2,有

i=1nfifi1fi2=i=1nfifi1(fifi1)=i=1nfi2fi1fifi12=fn2fn1fnfn12+fn12fn2fn1fn22++f12f0f1f02=fn2fn1(fn1+fn2)fn12+fn12fn2(fn2+fn3)fn22++00=fn2fn1fn13fn230=fn2fn1i=1n1fi3

故答案可以整理为

i=1n(fifi1fi2+fi3)+fnfn+1=fn2fn1i=1n1fi3+i=1nfi3+fnfn+1=fn2fn1+fn3+fnfn+1=fn2fn+1+fnfn+1

直接矩阵快速幂即可。
另外,这题也可以数形结合,用立方体的体积表示乘积,发现所有的 fifi1fi2fi3 可以互补成一个长宽高为 fn+1,fn,fn 的立方体,同样可以得出答案。

#include<bits/stdc++.h>
#define int long long
#define ll long long
#define int long long
int mod;
using namespace std;
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<<1)+(x<<3)+(ch^48);
return x*f;
}
inline int mo(int x){return x>=1e9?x%mod:x;}
struct mt{
ll a[2][2];
mt(){memset(a,0,sizeof a);}
mt operator*(const mt&b)const{
mt res;
for(int i=0;i<=1;++i)
for(int k=0;k<=1;++k){
int zc=a[i][k];
for(int j=0;j<=1;++j)
res.a[i][j]=mo(res.a[i][j]+zc*b.a[k][j]);
}
return res;
}
}ans,base;
inline int qpow(int b){
b--;
ans.a[0][0]=0,ans.a[0][1]=1;
base.a[0][1]=base.a[1][0]=base.a[1][1]=1;
base.a[0][0]=0;
while(b){
if(b&1)ans=ans*base;
base=base*base;b>>=1;
}
return ans.a[0][1];
}
signed main(){
// freopen("in.in","r",stdin);freopen("out.out","w",stdout);
int T=read();
while(T--){
int n=read();mod=read();
int a=qpow(n),b=ans.a[0][0]+a;
std::cout<<(a*a%mod*b%mod+b*a%mod)%mod<<'\n';
}
}

6.23

AT_abc359_d [ABC359D] Avoid K Palindrome

看到 k10,考虑状压,字母 A 代表 0,字母 B 代表 1? 都可以代表,设 fi,s 表示到达第 i 个位置,最后 k 个字母的状态为 s 时的方案数。
显然有转移方程 fi,s=fi1,(s>>1)+(1<<(k1))+fi1,s>>1,继承了前 k1 位的状态,check 一下状态 s 是否合法即可。

#include<bits/stdc++.h>
typedef long long ll;
typedef unsigned long long ull;
inline int read(){char ch=getchar();int x=0,f=1;for(;ch<'0'||ch>'9';ch=getchar())if(ch=='-')f=-1;for(;ch>='0'&&ch<='9';ch=getchar())x=(x<<3)+(x<<1)+(ch^48);return x*f;}
const int N=2e3+50,mod=998244353;
char st[N];
int n,k,f[N][N];
bool vis[N];
inline int mo(int x){return x<0?(x%mod+mod)%mod:(x>=mod?x%mod:x);}
inline bool check(int l,int r,int s){
int x=s;
for(int i=r;i>=l;--i){
int zc=s&1;s>>=1;
if(st[i]=='?')continue;
if(st[i]-'A'!=zc)return false;
}
return (!vis[x]);
}
signed main(){
// freopen("in.in","r",stdin);freopen("out.out","w",stdout);
std::ios::sync_with_stdio(false);std::cin.tie(0);std::cout.tie(0);
std::cin>>n>>k>>(st+1);
if(k==1){std::cout<<0<<'\n';exit(0);}
vis[0]=1;
for(int i=0;i<(1<<k)-1;++i){
int a[11],b[11];
for(int i=1;i<=k;++i)a[i]=b[i]=0;
int len=0,x=i;
while(x)len++,a[len]=b[len]=(x&1),x>>=1;
len=k;
std::reverse(b+1,b+len+1);
int tot=0;
for(int j=1;j<=len;++j)tot+=a[j]==b[j];
if(tot==len)vis[i]=1;
f[k][i]=check(1,k,i);
}
int ans=0;
for(int i=k+1;i<=n;++i)
for(int s=0;s<(1<<k)-1;++s)
if(check(i-k+1,i,s)){
int x=s>>1;
f[i][s]=mo(f[i][s]+f[i-1][x+(1<<k-1)]+f[i-1][x]);
}
for(int s=0;s<(1<<k)-1;++s)ans=mo(ans+f[n][s]);
std::cout<<ans<<'\n';
}

AT_abc359_e [ABC359E] Water Tank

如果一个水桶即将有水,那么之前的所有低于他的桶一定都装满了水,否则水不会过来。
所以需要维护一个高度单调递减的栈就行,就是单调栈板子题,时间复杂度 O(n)

#include<bits/stdc++.h>
#define int long long
typedef long long ll;
typedef unsigned long long ull;
inline int read(){char ch=getchar();int x=0,f=1;for(;ch<'0'||ch>'9';ch=getchar())if(ch=='-')f=-1;for(;ch>='0'&&ch<='9';ch=getchar())x=(x<<3)+(x<<1)+(ch^48);return x*f;}
const int N=2e5+10;
int n,h[N],f[N],head,tail,q[N];
signed main(){
// freopen("in.in","r",stdin);freopen("out.out","w",stdout);
std::ios::sync_with_stdio(false);std::cin.tie(0);std::cout.tie(0);
n=read();int ans=0;
h[0]=2e18;
for(int i=1;i<=n;++i){
h[i]=read();
int pos=0;
while(head<=tail&&h[i]>h[q[tail]]){--tail;}
pos=q[tail],q[++tail]=i;
f[i]=f[pos]+(i-pos)*h[i];
std::cout<<f[i]+1<<' ';
}
}

AT_abc359_f [ABC359F] Tree Degree Optimization

对于任何一个度数合法的方案,它所对应的树一定是存在的,所以只需要考虑度数的分配即可。
为了保证连通,每个点至少有一个度,答案先记上这一点。然后剩下 n2 个度要分配,考虑动态维护给每个点再加上一个度的贡献,贪心每次分配给贡献最小的即可。

#include<bits/stdc++.h>
#define int long long
typedef long long ll;
typedef unsigned long long ull;
inline int read(){char ch=getchar();int x=0,f=1;for(;ch<'0'||ch>'9';ch=getchar())if(ch=='-')f=-1;for(;ch>='0'&&ch<='9';ch=getchar())x=(x<<3)+(x<<1)+(ch^48);return x*f;}
const int N=2e5+10;
int n,a[N];
struct QQ
{
int val,cnt,id;
friend bool operator <(QQ a,QQ b){return a.val>b.val;}
};
std::priority_queue<QQ> q;
signed main(){
// freopen("in.in","r",stdin);freopen("out.out","w",stdout);
std::ios::sync_with_stdio(false);std::cin.tie(0);std::cout.tie(0);
n=read();int ans=0;
for(int i=1;i<=n;++i){
int x=read();
ans+=x;
q.push({x*3,1,x});
}
for(int i=1;i<=n-2;++i){
auto it=q.top();q.pop();
ans+=it.val;
int x=it.cnt+1;
q.push({it.id*(x*2+1),x,it.id});
}
std::cout<<ans<<'\n';
}
posted @   Ishar-zdl  阅读(58)  评论(1编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
点击右上角即可分享
微信分享提示