2024年6月杂题乱写
6.5
P3214 [HNOI2011] 卡农
设 \(f_i\) 表示选了 \(m\) 个集合的答案,简单观察发现,只要确定了 \(m-1\) 个集合,最后一个集合就是确定的,不是偶数次数的出现,偶数次数的不出现,选 \(m\) 个集合有 \(C_{2^n-1}^{m-1}\) 种方案,考虑下面两种不合法的情况。
- 这 \(m-1\) 个集合已经合法,最后一个集合为空集。
- 最后要选的集合在前面 \(m-1\) 个集合出现过。
答案就是总数减去这两种情况,第一种情况显然为 \(f_{i-1}\),第二种情况其实就是有 \(i-2\) 个集合已经合法了,数量为 \(f_{i-2}\),但是因为最后的一个集合(相同的两个)不确定,所以第二种情况实际上有 \(f_{i-2}\cdot[2^{n}-1-(i-2)]\),直接减去就可以,这时因为我们每一种都算了 \(m\) 遍,所以答案要除以 \(m\)。
直接递推即可。
点击查看代码
#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\) 十进制下的长度,\(V_n=n\cdot\sum_{i=0}^{n-1} 10^{i\cdot len}\),后面直接等比数列求和 \(ans=n\cdot\frac{10^{n\cdot len}-1}{10^{len}-1}\)。
- E 就是求有向图每个点能到达的点(包括自己)的和,反向建边后直接缩点拓扑排序跑 DP 即可。
- F 一眼线段树,赛时傻逼没推下式子锅了,\((a+x)\cdot (b+y)=ab+xb+ya+xy\),记两个 tag 即可。
- G 高级东西,不会。
6.15 ABC358 速通
D 直接二分找,然后删。
E 就是求多重集排列数,直接设 \(f_{i,j}\) 表示考虑前 \(i\) 个字母,长度为 \(j\) 时的方案数,枚举新加入多少个元素,有 \(f_{i,j}=\sum_{k=\max(0,j-c_i)}^{j}f_{i-1,j}\cdot C_j^k\),最后 \(ans=\sum_{i=1}^{n}f_{26,i}\)
F 随便找,然后比较傻逼,没意思。
G 直接设状态 \(f_{t,i,j}\) 表示 \(t\) 时刻在 \((i,j)\) 的最大价值,转移就直接从 \(5\) 个位置转移,发现 \(k\) 很大,但是当 \(k\ge nm\) 时就没啥意义了,因为此时的最优解一定不会再从其他点转移了,所以考虑 \(k\le nm\) 时的答案,最后加上剩下的次数乘上价值找最大就行。
6.17
感觉这种博客还是要写下去,
CF1808D Petya, Petya, Petr, and Palindromes
比较水的题。首先直接不太好想,正难则反,考虑将所有不需要改变的点对处理出来,拿总数一减就是答案。
发现合法数就是两个位置的数相同,所以就变成了,对于每一个数,查询指定区间内与它相等的数的个数。
因为需要满足回文的对称性,所以偶数位置只会和偶数位置匹配,奇数位置只会和奇数位置匹配,此时其实可以直接无脑上数据结构了,时间复杂度 \(\mathcal{O}(n\log n)\)。
不考虑数据结构,这里有两种处理方法。
- 开个桶记录一下每个数的出现次数,类似莫队的思想,两个指针记录一下桶的范围,每次查到 \(a_i\) 时,求出与 \(a_i\) 的对应位置范围,然后移动指针,如果位置 \(j\) 与 \(i\) 奇偶性相同,就产生 \(1\) 的贡献,奇数偶数各查一次。
- 开桶记录每个数出现的奇数位置和偶数位置,对于每个数,双指针不断向右扩展,直到不合法时记录一下两个指针之间的数的个数,也是奇数偶数都查一次。
这两种做法的时间复杂度都是 \(\mathcal{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\) 依次标号,这样由 \(b\) 到 \(a\) 的交换次数就是 \(b\) 的逆序对数量。题目就转化为了求逆序对数量最多的 \(b\) 串,但是同种字母之间是没有区别的,所以在最优策略下,同种字母直接是不会进行交换的,不属于逆序对,所以不能简单的将 \(a\) 串翻转。
有引理:一定存在所有同类字母相邻的最优答案。
感性理解:因为字母之间只会不同类交换,所以考虑把不同类的字母尽量放前面,同类在一起。
证明:
- 在最优策略下,同类字母不会发生交换,所以对于任意排列,同类字母之间的标号都是递增的。
- 对于同类字母 \(a_i\) 与 \(a_j\) 之间的所有相邻异类字母 \(a_k\) 到 \(a_q\) 来说,如果 \(a_k\) 这种字母中 \(w\) 个字母与前面的 \(a_i\) 这种字母共构成了 \(v_1\) 个逆序对,\(s\) 个与后面的 \(a_i\) 这种字母共构成了 \(v_2\) 个逆序对,有 \(v_1\le (k-i)\times w,v_2\le (j-q)\times s\)。如果将所有的 \(a_k\) 交换到前面,此时新增了 \([(k-i)\times s-v_1]\) 个逆序对,如果将他们交换到后面,此时新增了 \([(j-q)\times w-v_2]\) 个逆序对。
- 当 \((k-i)\times s-v_1\le 0\),即 \((k-i)\times s\le (k-i)\times w\) 时,有 \(s\le w\),则 \(v_2\le(j-q)\times s\le(j-q)\times w\),即 \((j-q)\times w-v_2\ge 0\)。同理,如果 \((j-q)\times w-v_2\le 0\),则 \((k-i)\times s-v_1\ge 0\)。
- 所以对于任何一种情况,我们总能使得同类字母相邻且逆序对数量单调不减,即一定存在所有同类字母相邻的最优答案。
由于不确定字母之间的顺序,我们可以直接枚举排列,然后每次用树状数组查询逆序对数量(当然也可以 \(\mathcal{O}(n)\) 直接计算交换次数),取最大即可,时间复杂度 \(\mathcal{O}(24n\log 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=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 对不出脑电波,感觉赛时死磕很不好。
有一点比较显然,如果一个人不能直接获胜的话,那么他前面的人都应该被撤下,这样才会改变这个人的票数,才有可能使他获胜。
接下来有两种情况:
- 撤完前面的人后,他的票数大于等于剩下的人的最多票数的话,那么此时直接获胜。
- 撤完前面的人后,他的票数小于剩下的人的最多票数的话,此时再把票数最多的人撤下,这样他就成了票数最多的人,获胜。
直接做前缀和和后缀最大值就行,时间复杂度 \(\mathcal{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
感觉是对脑电波题,能对上黄,对不上紫。
首先对于 \(1\) 和 \(0\) 的个数是奇数的话一定不合法,特判掉即可。
经过一些数据的模拟,我们好像并不能找出完全不合法的情况,这时可以大胆猜测如果数量不是奇数的话一定有解。
考虑什么样的序列一定是合法的,不难发现,两位两位的进行分组,每组都一样的序列一定是合法的,如 1111001100 或 0000111100,对于答案 \(p\),直接选奇数位就可以。
事实上,对于任意一个可能合法的序列,都能构造出一种方案,使得他与上面序列同构。
具体来说,我们扫描每一组,如果这两个数不一样,选出一个和上一个选择不一样的那一位(如果上次没选过就随便选),扫描完毕后,将选出的子序列右移一位。此时每组的两个数都相同,合法。
考虑为什么第一组的数也一定相同:因为 \(1\) 和 \(0\) 的个数为偶数,所以选出来的数一定有偶数个 \(0\) 和 \(1\),并且他们是交错的,所以右移之后一定能使每一组的数都相同。
#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』高维立方体
更多关于斐波那契数列的知识
设 \(f_i\) 表示斐波那契数列的第 \(i\) 项。
引理一:\(\sum_{i=1}^{n}f^2_i=f_nf_{n+1}\)
证明:显然 \(\sum_{i=1}^{n}f^2_i=f_nf_{n+1}\) 对于 \(n=3\) 成立,假设该引理对于 \(n=k\) 成立,对于 \(n=k+1\),有
即 \(\sum_{i=1}^{n}f^2_i=f_nf_{n+1}\),证毕。
简单整理下所求式子,得 \(\sum_{i=1}^n(f_if_{i-1}f_{i-2}+f^3_i)+f_nf_{n+1}\)
对于 \(\sum_{i=1}^n f_if_{i-1}f_{i-2}\),有
故答案可以整理为
直接矩阵快速幂即可。
另外,这题也可以数形结合,用立方体的体积表示乘积,发现所有的 \(f_if_{i-1}f_{i-2}\) 和 \(f^3_i\) 可以互补成一个长宽高为 \(f_{n+1},f_n,f_n\) 的立方体,同样可以得出答案。
#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
看到 \(k\le 10\),考虑状压,字母 A 代表 \(0\),字母 B 代表 \(1\),? 都可以代表,设 \(f_{i,s}\) 表示到达第 \(i\) 个位置,最后 \(k\) 个字母的状态为 \(s\) 时的方案数。
显然有转移方程 \(f_{i,s}=f_{i-1,(s>>1)+(1<<(k-1))}+f_{i-1,s>>1}\),继承了前 \(k-1\) 位的状态,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
如果一个水桶即将有水,那么之前的所有低于他的桶一定都装满了水,否则水不会过来。
所以需要维护一个高度单调递减的栈就行,就是单调栈板子题,时间复杂度 \(\mathcal{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
对于任何一个度数合法的方案,它所对应的树一定是存在的,所以只需要考虑度数的分配即可。
为了保证连通,每个点至少有一个度,答案先记上这一点。然后剩下 \(n-2\) 个度要分配,考虑动态维护给每个点再加上一个度的贡献,贪心每次分配给贡献最小的即可。
#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';
}

浙公网安备 33010602011771号