\(\text{T1}\) GirlFriend区间 3
好题。
先把质数筛了。
考虑将所有区间按照左右端点离散化。将询问离线下来,然后对于每个右端点统计左端点上的贡献。即从小到大扫描 \(r\),维护每一个后缀的答案。
考虑使用 set 维护区间的并。考虑已处理前 \(r-1\) 的询问,处理位于 \(r\) 上的所有右端点的询问产生的影响。那么对于 \([i,r-1]\) 的区间并,\(r\) 的区间产生的贡献覆盖的是 \([i,r]\) 的并减去 \([i,r-1]\) 的区间。也就是空出来的部分。然后使用树状数组维护。
注意到对于一个之前的区间 \(i\),它最多被 update 覆盖两次(左端点被覆盖时一次,右端点一次)。所以颜色段均摊 \(O((n+m)\log n)\)。
代码还没写完。
upd:你妈的不写了。
\(\text{T2}\) ABC小精灵
考虑 AB
通过 ABAA->ABABBA->BA
可以变成 BA
。同理 BC
可以变成 CB
。
那么 A
与 B
,C
与 B
可以相互交换。那么 B
就可以随意放置。将 B
扔到最前面,然后 A,C
相对位置无法交换。所以最终序列必然形如 B...BACAACCAA...
,对于相邻两个相同的字符再删去,比较最终序列即可判断。
#include <bits/stdc++.h>
using namespace std;
const int N=5e5+6;
int T,n,m,B1,B2,top1,top2;
char s1[N],s2[N],t1[N],t2[N];
inline void solve(){
B1=B2=top1=top2=0;
scanf("%s%s",s1+1,s2+1),n=strlen(s1+1),m=strlen(s2+1);
for(int i=1;i<=n;i++){
if(s1[i]=='B')B1++;
else{
if(s1[i]==t1[top1])top1--;
else t1[++top1]=s1[i];
}
}
for(int i=1;i<=m;i++){
if(s2[i]=='B')B2++;
else{
if(s2[i]==t2[top2])top2--;
else t2[++top2]=s2[i];
}
}
if(top1!=top2||(B1-B2)&1){
puts("NO");
return;
}
for(int i=1;i<=top1;i++)if(t1[i]!=t2[i]){
puts("NO");
return;
}
puts("YES");
return;
}
int main(){
scanf("%d",&T);
while(T--)solve();
}
\(\text{T3}\) 树的计数
显然是个笛卡尔树相关。然后对于多个相同的 min 必定形如一棵二叉树。所以对于一段区间其贡献为相同的 min 的个数。使用卡特兰数计算。然后多个 min 会将一个区间分割为多个区间,那么递归求解。对于区间 min 与 min 的位置使用线段树实现。当然你也可以用 ST表+set 一只 \(\log\)。
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define ls p<<1
#define rs p<<1|1
#define pb push_back
const int N=1e6+7;
const int mod=1e9+7;
int n,a[N],fac[N<<1],ifac[N<<1],tr[N<<2],mn;
vector<int>pos[N<<2],mnpos;
inline int qpow(int a,int b){
int res=1;
while(b){
if(b&1)res=res*a%mod;
a=a*a%mod;
b>>=1;
}
return res;
}
inline void init(int n){
fac[0]=ifac[0]=1;
for(int i=1;i<=n;i++)fac[i]=fac[i-1]*i%mod;
ifac[n]=qpow(fac[n],mod-2);
for(int i=n-1;i;i--)ifac[i]=ifac[i+1]*(i+1)%mod;
return;
}
inline int C(int n,int m){
return fac[n]*ifac[m]%mod*ifac[n-m]%mod;
}
inline int Cat(int n){
return (C(2*n,n)-C(2*n,n-1)+mod)%mod;
}
inline void merge(vector<int>&x,int y){
for(auto i:pos[y])x.pb(i);
return;
}
inline void pushup(int p){
tr[p]=min(tr[ls],tr[rs]);
if(tr[p]==tr[ls])merge(pos[p],ls);
if(tr[p]==tr[rs])merge(pos[p],rs);
return;
}
inline void build(int p,int l,int r){
if(l==r){
tr[p]=a[l],pos[p].pb(l);
return;
}
int mid=(l+r)>>1;
build(ls,l,mid),build(rs,mid+1,r);
pushup(p);
return;
}
inline void query(int p,int l,int r,int s,int t){
if(s>t)return;
if(s<=l&&r<=t){
if(mn>tr[p])mn=tr[p],mnpos=pos[p];
else if(mn==tr[p])merge(mnpos,p);
return;
}
int mid=(l+r)>>1;
if(s<=mid)query(ls,l,mid,s,t);
if(t>mid)query(rs,mid+1,r,s,t);
return;
}
inline int solve(int l,int r){
if(l>=r)return 1;
mnpos.clear(),mn=LLONG_MAX;
query(1,1,n,l,r);
int ans=1,num=0,lst=l;
vector<pair<int,int> >vec;
vec.clear();
for(auto i:mnpos){
num++;
vec.push_back(make_pair(lst,i-1));
lst=i+1;
}
for(auto i:vec)ans=ans*solve(i.first,i.second)%mod;
ans=ans*solve(lst,r)%mod;
ans=ans*Cat(num)%mod;
return ans;
}
signed main(){
scanf("%lld",&n);init(N-1);
for(int i=1;i<=n;i++)scanf("%lld",&a[i]);
build(1,1,n);
printf("%lld",solve(1,n));
return 0;
}
\(\text{T4}\) AND=OR
考虑将两个区间独立开然后依次与。那么显然 popcount 是单调不升的。同理对于或的情况单调不减。
所以对于一段区间,按照 popcount 排序,选出的必然是选择一段前缀与后缀。
枚举两边相等数的 popcount \(k\),查询区间内 popcount 为 \([0,k-1]\) 的或和与 \([k+1,30]\) 的数的与和。注意对于 popcount 为 \(k\) 的数必然全部相同。否则无解。对于每个 \(k\) 使用线段树维护。
#include<bits/stdc++.h>
using namespace std;
#define ls p<<1
#define rs p<<1|1
const int N=1e5+6;
const int M=32;
int n,Q,mxpc,And_[M],Or_[M],Cnt_[M],a[N];
struct SegmentTree{
int And[N<<2],Or[N<<2],tr[N<<2];
inline void pushup(int p){
And[p]=And[ls]&And[rs];
Or[p]=Or[ls]|Or[rs];
tr[p]=tr[ls]+tr[rs];
return;
}
inline void build(int p,int l,int r){
if(l==r){
And[p]=-1,Or[p]=tr[p]=0;
return;
}
int mid=(l+r)>>1;
build(ls,l,mid),build(rs,mid+1,r);
pushup(p);
}
inline void update(int p,int l,int r,int ps,int val){
if(l==r){
And[p]=Or[p]=val,tr[p]=1;
return;
}
int mid=(l+r)>>1;
if(ps<=mid)update(ls,l,mid,ps,val);
else update(rs,mid+1,r,ps,val);
pushup(p);
}
inline int queryand(int p,int l,int r,int s,int t){
if(s<=l&&r<=t)return And[p];
int mid=(l+r)>>1,res=-1;
if(s<=mid)res&=queryand(ls,l,mid,s,t);
if(t>mid)res&=queryand(rs,mid+1,r,s,t);
return res;
}
inline int queryor(int p,int l,int r,int s,int t){
if(s<=l&&r<=t)return Or[p];
int mid=(l+r)>>1,res=0;
if(s<=mid)res|=queryor(ls,l,mid,s,t);
if(t>mid)res|=queryor(rs,mid+1,r,s,t);
return res;
}
inline int query(int p,int l,int r,int s,int t){
if(s<=l&&r<=t)return tr[p];
int mid=(l+r)>>1,res=0;
if(s<=mid)res+=query(ls,l,mid,s,t);
if(t>mid)res+=query(rs,mid+1,r,s,t);
return res;
}
}ch[M];
inline int pc(int x){
if(x<0)return 47497;
int res=0;
while(x){
res+=x&1;
x>>=1;
}
return res;
}
int main(){
scanf("%d%d",&n,&Q);
for(int i=0;i<=30;i++)ch[i].build(1,1,n);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
int x=pc(a[i]);
mxpc=max(mxpc,x);
ch[x].update(1,1,n,i,a[i]);
}
while(Q--){
int l,r,flag=0;
scanf("%d%d",&l,&r);
for(int i=0;i<=mxpc;i++){
And_[i]=ch[i].queryand(1,1,n,l,r);
Or_[i]=ch[i].queryor(1,1,n,l,r);
Cnt_[i]=ch[i].query(1,1,n,l,r);
}
for(int i=0;i<=mxpc;i++){
int Sor=0,Sand=-1,Cntor=0,Cntand=0;
for(int j=0;j<i;j++)Sor|=Or_[j],Cntor+=Cnt_[j];
for(int j=i;j<=mxpc;j++)Sand&=And_[j],Cntand+=Cnt_[j];
if(Sor==Sand&&Cntor>0&&Cntand>0){
puts("YES");flag=1;
break;
}
if(pc(And_[i])!=i)continue;
if(Cnt_[i]>1&&(Sor|And_[i])==(Sand)){
puts("YES");flag=1;
break;
}
}
if(!flag)puts("NO");
}
return 0;
}
\(\text{T5}\) 数学题
如 FWT 按照 \(2\) 的次幂为合并长度合并答案。然后直接子集 dp。
具体实现请参考:https://codeforces.com/blog/entry/45223
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=6e5+6;
const int mod=1e9+7;
const int lg=20;
int n,a[N],f[N],g[N],sf[N][lg+1],sg[N][lg+1],ans;
signed main(){
scanf("%lld",&n);
for(int i=0;i<n;i++)scanf("%lld",&a[i]);
for(int i=0;i<n;i++){
sf[i][lg]=sg[i][lg]=0;
for(int j=lg-1;~j;j--){
sf[i][j]=sf[i][j+1],sg[i][j]=sg[i][j+1];
int s=1<<j;
if(i&s){
sf[i][j]=(sf[i][j]+sf[i-s][j])%mod;
sg[i][j]=(sg[i][j]+sg[i-s][j])%mod;
}
}
f[i]=(a[i]*a[i]%mod+sg[i][0]*sg[i][0]%mod)%mod;
g[i]=(f[i]*f[i]%mod+sf[i][0])%mod;
for(int j=lg;~j;j--){
sf[i][j]=(sf[i][j]+f[i]*f[i]%mod)%mod;
sg[i][j]=(sg[i][j]+g[i])%mod;
}
ans=(ans+f[i]*g[i]%mod*i%mod)%mod;
}
printf("%lld",ans);
return 0;
}
\(\text{T6}\) 考试
先把理论考试全部选择,然后就变为从 \(n\) 个 \(b_i-a_i\) 中至少选择 \(B\) 个的最大代价。显然通过贪心容易。
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+6;
int n,A,B,a[N],b[N],ans;
inline bool cmp(int x,int y){return x>y;}
int main(){
scanf("%d%d%d",&n,&A,&B);
for(int i=1;i<=n;i++)scanf("%d",&a[i]),ans+=a[i];
for(int i=1;i<=n;i++)scanf("%d",&b[i]),b[i]-=a[i];
sort(b+1,b+n+1,cmp);
for(int i=1;i<=B;i++)ans+=b[i];
for(int i=B+1;i<=n&&n-i>=A;i++)if(b[i]>0)ans+=b[i];
printf("%d",ans);
return 0;
}
\(\text{T7}\) 背包
注意到 \(m\) 很小,考虑状压 dp。\(f_{i,S}\) 表示第 \(i\) 个背包,\(S\) 表示选择的物品集合。这样是 \(O(2^nn^2)\)。考虑优化。令 \(f_{S},g_{S}\),其中 \(f\) 表示 \(S\) 状态下最少选择的背包数量,\(g\) 表示当前最后加入的这个背包最大还可以留下多少空间。此时 \(O(2^nn)\),可以通过。
#include<bits/stdc++.h>
using namespace std;
const int N=2e6+7;
const int M=100;
int n,m,a[M],w[M],f[N],g[N],S;
inline bool cmp(int x,int y){return x>y;}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)scanf("%d",&a[i]);
for(int i=1;i<=m;i++)scanf("%d",&w[i]);
sort(w+1,w+m+1,cmp);
f[0]=1,g[0]=w[1];S=(1<<n)-1;
for(int i=0;i<=S;i++){
if(!f[i])continue;
for(int j=1;j<=n;j++){
int I=i|(1<<j-1);
if(i==I)continue;
if(g[i]>=a[j]){
if(!f[I]||f[i]<f[I]){
f[I]=f[i];
g[I]=g[i]-a[j];
}
else if(f[I]==f[i])g[I]=max(g[I],(g[i]-a[j]));
}
else if(f[i]!=m){
int k=f[i]+1;
if(w[k]>=a[j]){
if(f[I]>f[i]+1||!f[I]){
f[I]=f[i]+1;
g[I]=w[k]-a[j];
}
else if(f[I]==f[i]+1)g[I]=max(g[I],w[k]-a[j]);
}
}
}
}
if(!f[S])puts("-1");
else printf("%d",f[S]);
return 0;
}
\(\text{T8}\) 移动细胞
\(f_{i,j}\) 表示第 \(i\) 列,第 \(i\) 列黑色细胞的上端放在第 \(j\) 行。
\[f_{i,j}=\min_{k=j-len_{i-1}+1}^{j-len_i+1} f_{i-1,k}+|l_i-j| \]直接 dp \(O(n^2)\),考虑单调队列优化即可。
#include <bits/stdc++.h>
using namespace std;
const int N=1e5+6;
int n,m,l[N],r[N],len[N],ans=INT_MAX,dp[2][N],q[N];
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++){
scanf("%d%d",&l[i],&r[i]);
len[i]=r[i]-l[i]+1;
}
for(int i=1;i<=m;i++){
int I=(i&1),head=1,tail=0;
for(int j=1;j<len[i];j++){
while(head<=tail&&dp[I^1][q[tail]]>dp[I^1][j])tail--;
q[++tail]=j;
}
for(int j=1;j<=n-len[i]+1;j++){
while(head<=tail&&q[head]<j-len[i-1]+1)head++;
while(head<=tail&&dp[I^1][q[tail]]>dp[I^1][j+len[i]-1])tail--;
q[++tail]=j+len[i]-1;
dp[I][j]=min(dp[I^1][q[head]],dp[I^1][j+len[i]-1])+abs(l[i]-j);
}
for(int j=n-len[i]+2;j<=n;j++)dp[I][j]=INT_MAX;
}
for(int i=1;i<=n-len[m]+1;i++)ans=min(ans,dp[m&1][i]);
printf("%d",ans);
return 0;
}
\(\text{T9}\)
\(\text{T1}\) 经典原题,不讲。
\(\text{T2}\) 雪猫头鹰
直接 dp。记录上个断点与前缀 min 的 pos 集合。使用 st表 \(O(n^2)\)。使用线段树 \(O(n\log n)\)。啥ds都不用 \(O(n)\)。然而我是大怨种。
#include<bits/stdc++.h>
using namespace std;
#define ls p<<1
#define rs p<<1|1
const int N=5e5+4;
const int mod=1e9+7;
int n,a[N],tr[N<<2],f[N],mx,mxpos,pos[N<<2],g[N];
inline void pushup(int p){
tr[p]=max(tr[ls],tr[rs]);
if(tr[p]==tr[rs])pos[p]=pos[rs];
if(tr[p]==tr[ls])pos[p]=pos[ls];
return;
}
inline void build(int p,int l,int r){
if(l==r){
tr[p]=a[l],pos[p]=l;
return;
}
int mid=(l+r)>>1;
build(ls,l,mid),build(rs,mid+1,r);
pushup(p);
return;
}
inline void query(int p,int l,int r,int s,int t){
if(s>t)return;
if(s<=l&&r<=t){
if(mx<tr[p])mx=tr[p],mxpos=pos[p];
return;
}
int mid=(l+r)>>1;
if(s<=mid)query(ls,l,mid,s,t);
if(t>mid)query(rs,mid+1,r,s,t);
return;
}
signed main(){
scanf("%lld",&n);
for(int i=1;i<=n;i++)scanf("%lld",&a[i]);
build(1,1,n);f[0]=1,g[0]=1;
for(int i=1;i<=n;i++){
mx=0,mxpos=0;
query(1,1,n,1,i-1);
if(mx>a[i])f[i]=g[mxpos-1];
else f[i]=g[i-1];
g[i]=g[i-1]+f[i];
f[i]%=mod,g[i]%=mod;
// f[i]=g[mxpos]
}
printf("%lld",f[n]);
return 0;
}
\(\text{T3}\) 白头鹰
什么诈骗题。赛时就我过了就离谱。
使用打表容易发现当 \(k\) 至 \(a+1\) 后 \(f\) 恒为 \(1\)。然后 \(a\) 很小。于是做完了。
#include<bits/stdc++.h>
using namespace std;
#define int long long
int b,a,p;
inline int qpow(int a,int b){
int res=1;
while(b){
if(b&1)res=res*a%p;
a=a*a%p;
b>>=1;
}
return res;
}
inline int f(int x,int y){
int v=a*b+x*y;
if(v%p==0)return 1;
else return (a*x+b*y)%p*qpow(v%p,p-2)%p;
}
inline int F(int a,int b,int p,int k){
int lst=k;
for(int i=k-1;i;i--){
int x=f(lst,i);
lst=x;
}
return lst;
}
signed main(){
int k;
cin>>a>>b>>p>>k;int mx=max(a,b);
if(k>a)cout<<F(a,b,p,a+1);
else cout<<F(a,b,p,k);
// for(int i=1;i<=n;i++)
}
//signed main(){
// srand(time(0));
//
// a=2,b=4,p=5;
// int T;cin>>T;
// for(int k=T;k;--k) cout<<F(a,b,p,k)<<" "<<k<<endl;
// cout<<a<<" "<<b<<" "<<p<<"?\n";
//// solved();
//}
\(\text{T4}\) 鸵鸟
发现我们对于每个加号,可以使用后面的 \(i\) 来匹配这里的加号。\(f_{i,j}\) 表示前 \(i\) 个还有 \(j\) 个加号没有匹配。当前位若是减号则匹配之前的加号。
我的评价是:如果想到加号匹配,那么一眼丁真。
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=5e3+4;
const int mod=1e9+7;
int n,dp[N][N],a[N],cnt;
char s[N];
signed main(){
scanf("%s",s+1),n=strlen(s+1);
for(int i=1;i<=n;i++)a[i]=s[i]=='+';
dp[0][0]=1;
for(int i=1;i<=n;i++){
cnt+=a[i];
for(int j=0;j<=cnt;j++){
if(a[i])dp[i][j]=(dp[i][j]+dp[i-1][j-1]+j*dp[i-1][j]%mod)%mod;
else dp[i][j]=(dp[i][j]+dp[i-1][j]*j%mod+dp[i-1][j+1]*(j+1)%mod*(j+1)%mod)%mod;
}
}
printf("%lld",dp[n][0]);
return 0;
}
\(\text{T5}\) 帝企
注意到最后肯定是每个极长的只含有两种字符的连续段,然后这个连续段的字符不可能移动到前一个或后一个连续段。如 ..PSS|SR..
,那么每个极长段由于只包含两种字符(若三种则必然可以拆分或合并),直接冒泡排序。
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+7;
int n,k,q[N],a[N],top,res,lst=1,m;
map<char,int>mps;
char s[N],ans[N];
signed main(){
scanf("%d%d%s",&n,&k,s+1);
for(int i=1;i<=n;i++){
if(s[i]=='R')a[i]=1;
else if(s[i]=='S')a[i]=2;
else a[i]=3;
}
for(int i=1;i<=n+1;i++){
if(!mps[a[i]])res++;
mps[a[i]]++;
if(res==3){
mps[a[i]]--,res--;top=0;int o=0;
if(mps[1]&&mps[2])o=1;
if(mps[2]&&mps[3])o=2;
if(mps[3]&&mps[1])o=3;
for(int j=lst;j<i;j++){
if(top<k&&a[j]==o)q[++top]=j;
else ans[++m]=s[j];
}
for(int j=1;j<=top;j++)ans[++m]=s[q[j]];
for(int j=lst;j<i;j++)if(!(--mps[a[j]]))res--;
if(!mps[a[i]])res++;
mps[a[i]]++;lst=i;
}
}
for(int i=lst;i<=n;i++)ans[i]=s[i];
printf("%s",ans+1);
return 0;
}
\(\text{T1}\) 糖果
发现肯定是见到一对可以消除的就消除。显然消除顺序对于答案无关。所以顺着跑一遍,将能删除的直接删除,或者与栈顶的数配对,若配对失败则也扔到栈中。最后在栈中元素首尾配对来计算反向配对的答案。
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+6;
int n,m,a[N],ans,top,stk[N];
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)scanf("%d",&a[i]);
for(int i=1;i<=n;i++){
if(top&&(stk[top]==a[i]||stk[top]+a[i]==m))top--,ans++;
else stk[++top]=a[i];
}
for(int i=1;i<=top;i++){
if(stk[i]==stk[top-i+1]||stk[i]+stk[top-i+1]==m)ans++;
else break;
}
printf("%d",ans);
return 0;
}
\(\text{T2}\) 铜川
考虑给定序列怎么求有多少子区间和 \(≡0\pmod k\)。
记录前缀和为 \([0,k-1]\) 的每种前缀出现次数 \(c\),那么答案是 \(\binom{c}{2}\)。即对于任意两个同余的前缀,其区间必然整除 \(k\)。
\(f_{i,j,t}\) 表示当前计算前缀和为 \(i\),放了 \(j\) 个位置,好序列个数为 \(t\)的方案数。
\[f_{i,j,t}\times \binom{n-j}{x}\to f_{i+1,j+x,t+\binom{x}2} \]#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=66;
const int mod=998244353;
int n,k,tt,fac[N],ifac[N],f[N][N][N*N];
inline int qpow(int a,int b){
int res=1;
while(b){
if(b&1)res=1ll*res*a%mod;
a=1ll*a*a%mod;
b>>=1;
}
return res;
}
inline void init(int n){
fac[0]=ifac[0]=1;
for(int i=1;i<=n;i++)fac[i]=1ll*fac[i-1]*i%mod;
ifac[n]=qpow(fac[n],mod-2);
for(int i=n-1;i;i--)ifac[i]=1ll*ifac[i+1]*(i+1)%mod;
return;
}
inline int C(int n,int m){
return 1ll*fac[n]*ifac[m]%mod*ifac[n-m]%mod;
}
signed main(){
init(N-1);
scanf("%lld%lld%lld",&n,&k,&tt);
for(int i=0;i<=n;i++)f[0][i][i*(i+1)>>1]=C(n,i);
for(int i=1;i<k;i++){
for(int j=0;j<=n;j++){
for(int t=0;t<=((j+1)*j>>1);t++){
for(int jj=0;jj<=j;jj++){
int o=((j-jj)*(j-jj-1)>>1);
if(t<o)continue;
f[i][j][t]=(f[i][j][t]+f[i-1][jj][t-o]*C(n-jj,j-jj)%mod)%mod;
}
}
}
}
printf("%lld",f[k-1][n][tt]);
return 0;
}
\(\text{T3}\) 山峰
发现考虑确定最大值的位置不好做。发现最小值的位置唯一,一定在最左或最右。然后考虑将当前的 min 移到一方端点,然后删除 min,因为对答案不可能再产生影响。如此做 \(n\) 次,使用线段树维护当前第 \(i\) 位到第 \(1\) 位与第 \(n\) 位之间还剩下多少数。
#include<bits/stdc++.h>
using namespace std;
#define ls p<<1
#define rs p<<1|1
#define int long long
const int N=2e5+6;
int n,lsh[N],a[N],ans,tr[N<<2],pos[N];
inline void pushup(int p){
tr[p]=tr[ls]+tr[rs];
return;
}
inline void update(int p,int l,int r,int ps,int val){
if(l==r){
tr[p]+=val;
return;
}
int mid=(l+r)>>1;
if(ps<=mid)update(ls,l,mid,ps,val);
else update(rs,mid+1,r,ps,val);
pushup(p);
return;
}
inline int query(int p,int l,int r,int s,int t){
if(s>t)return 0;
if(s<=l&&r<=t)return tr[p];
int mid=(l+r)>>1,res=0;
if(s<=mid)res+=query(ls,l,mid,s,t);
if(t>mid)res+=query(rs,mid+1,r,s,t);
return res;
}
signed main(){
scanf("%lld",&n);
for(int i=1;i<=n;i++){
scanf("%lld",&a[i]);
lsh[i]=a[i];
}
sort(lsh+1,lsh+n+1);
for(int i=1;i<=n;i++)a[i]=lower_bound(lsh+1,lsh+n+1,a[i])-lsh;
for(int i=1;i<=n;i++)update(1,1,n,i,1),pos[a[i]]=i;
for(int i=1;i<=n;i++){
int j=pos[i];
ans+=min(query(1,1,n,1,j-1),query(1,1,n,j+1,n));
update(1,1,n,j,-1);
}
printf("%lld",ans);
return 0;
}
\(\text{T4}\) 瓦片
注意到 \(k\le 5\)。于是分类讨论。没了。
事实上可以发现答案是关于 \(n,m\) 的多项式,于是暴力跑 \(n,m\) 较小的情况,搜出来后插值。
我的评价:厂。
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int mod=998244353;
int n,m,k;
inline int qpow(int a,int b){
int res=1;
while(b){
if(b&1)res=res*a%mod;
a=a*a%mod;
b>>=1;
}
return res;
}
inline int C(int n,int m){
int res=1;
for(int i=1;i<=m;i++)res=res*(n-i+1)%mod;
for(int i=1;i<=m;i++)res=res*qpow(i,mod-2)%mod;
return res;
}
inline int sum(vector<int>vec){
int ans=0;
for(auto i:vec)ans=(ans+i)%mod;
return ans;
}
inline void sub1(){
puts("1");
return;
}
inline void sub2(){
printf("%lld",(n+m-2)%mod);
return;
}
inline void sub3(){
int ans=0;
ans=(C(n-1,2)+C(m-1,2))%mod;
ans=(ans+(n-1)*(m-1)%mod*4%mod)%mod;
printf("%lld",ans);
return;
}
inline void sub4(){
int ans=0,x=0,y=0;
ans=(n-1)*(m-1)%mod+(C(n-1,3)+C(m-1,3))%mod;
x=(C(n-1,2)*(m-1)%mod*3%mod+C(m-1,2)*(n-1)%mod*3%mod);
y=(n-1)*C(m-1,2)%mod*4%mod+(m-1)*C(n-1,2)%mod*4%mod;y=y*2%mod;y=(y+ans)%mod;
printf("%lld",(x+y)%mod);
return;
}
inline void sub5(){
vector<int>ly={
C(n-1,2)*C(m-1,2)%mod*2%mod,C(n-1,4),C(m-1,4),
C(n-1,3)*(m-1)%mod*4%mod,C(m-1,3)*(n-1)%mod*4%mod,
C(n-1,3)*(m-1)%mod*6%mod,C(m-1,3)*(n-1)%mod*6%mod,
C(n-1,2)*C(m-1,2)%mod*3%mod*2%mod,C(m-1,2)*C(n-1,2)%mod*3%mod*2%mod,
C(n-1,2)*(m-1)%mod*3%mod,C(m-1,2)*(n-1)%mod*3%mod,
C(n-1,2)*C(m-1,2)%mod*3%mod,C(m-1,2)*C(n-1,2)%mod*3%mod,
C(n-1,2)*C(m-1,2)%mod*8%mod,C(m-1,2)*C(n-1,2)%mod*8%mod,
C(n-1,3)*(m-1)%mod*4%mod,C(m-1,3)*(n-1)%mod*4%mod,
C(n-1,3)*(m-1)%mod*4%mod,C(m-1,3)*(n-1)%mod*4%mod,
C(n-1,2)*C(m-1,2)%mod*6%mod,C(m-1,2)*C(n-1,2)%mod*6%mod,
C(n-1,3)*(m-1)%mod*2%mod,C(m-1,3)*(n-1)%mod*2%mod,
C(n-1,3)*(m-1)%mod*6%mod,C(m-1,3)*(n-1)%mod*6%mod,
C(n-1,2)*(m-1)%mod*4%mod,C(m-1,2)*(n-1)%mod*4%mod,
C(n-1,2)*C(m-1,2)%mod*8%mod,C(m-1,2)*C(n-1,2)%mod*8%mod
};
printf("%lld",sum(ly));
return;
}
signed main(){
scanf("%lld%lld%lld",&n,&m,&k);
if(k==1)sub1();
else if(k==2)sub2();
else if(k==3)sub3();
else if(k==4)sub4();
else sub5();
return 0;
}
\(\text{T5}\) 聊天
首先将区间分为若干个连通块, 对于每个联通块内部必然有 \(r_i\ge l_{i+1}\) 成立。然后考虑让相邻两个连通块之间损失的代价最小,令当前连通块右端点 \(R\) 与下一个连通块左端点 \(L'\) 尽量接近。
按照右端点排序,对于当前的 \(r_{i-1}\ge l_{i}\) 则将区间加入当前连通块;若 \(r_{i-1}<l_{i}\) 则将区间 \(i\) 当作新的连通块。注意到有情况 \(l_j<l_i,j>i\) 时,区间 \(j\) 完全覆盖 \(i\),那么区间 \(i\) 没有任何作用。
那么先按照 \(l\) 排序后记录每个区间是否大于之前的 \(\max r\),若不是则必然被之前的某个区间所包含,于是打个标记。
那么上述过程可以通过 deque 实现。加入当前连通块时或者此连通块作为新的连通块的起点则扔到后面,否则扔到最前(即被覆盖的区间)。
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=5e5+6;
int n,lst,mx,ans[N],sum;
struct node{
int l,r,id,mx;
}q[N];
deque<node>dq;
inline bool cmp1(node a,node b){
return a.l!=b.l?a.l<b.l:a.r>b.r;
}
inline bool cmp2(node a,node b){
return a.r!=b.r?a.r<b.r:a.l<b.l;
}
signed main(){
scanf("%lld%lld",&n,&lst);
for(int i=1;i<=n;i++){
scanf("%lld%lld",&q[i].l,&q[i].r);
q[i].id=i;
}
sort(q+1,q+n+1,cmp1);
for(int i=1;i<=n;i++)if(q[i].r>mx)mx=q[i].r,q[i].mx=1;
sort(q+1,q+n+1,cmp2);dq.push_back(q[1]);
for(int i=2;i<=n;i++){
if(dq.back().r<q[i].l&&!q[i].mx)dq.push_front(q[i]);
else dq.push_back(q[i]);
}
for(int i=1;i<=n;i++){
node tmp=dq.front();dq.pop_front();
ans[i]=tmp.id;
sum+=max(lst-tmp.l,0ll),lst=tmp.r;
}
printf("%lld\n",sum);
for(int i=1;i<=n;i++)printf("%lld ",ans[i]);
return 0;
}
10.3省选组训练
\(\text{T1}\) Z
当 \(n\) 为偶数构造 \(2\times n,n\),当 \(n\) 为奇数,枚举质数 \(\min p\) 满足 \(p\) 不是 \(n\) 的因子则构造 \(pn,(p-1)n\)。
#include<bits/stdc++.h>
using namespace std;
#define int long long
int p[30]={2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97},T,n;
signed main(){
scanf("%lld",&T);
while(T--){
scanf("%lld",&n);
if(n&1){int i;
for(i=1;i<25;i++)if(n%p[i])break;
printf("%lld %lld\n",n*p[i],(p[i]-1)*n);
}
else printf("%lld %lld\n",n*2,n);
}
return 0;
}
国庆联赛模拟赛DAY4
\(\text{T1}\) 区间
同 CF1285E Delete a Segment。
所有区间按照端点离散化。
考虑对于一段新的区间 \([l,r]\),而此区间属于原来的区间 \([L,R]\)。若 \([l,r]\) 上没有其他的区间覆盖则会贡献答案至区间 \([L,R]\)。注意若 \(l=L\) 或 \(r=R\) 时无贡献。所以用 set 扫描线即可。
#include<bits/stdc++.h>
using namespace std;
const int N=4e5+6;
int T,n,m,ans,cnt[N],mx;
pair<int,int>q[N];
set<int>st;
inline void solve(){
scanf("%d",&n);ans=m=0;st.clear();mx=-1;
for(int i=1;i<=n;i++){
int l,r;
scanf("%d%d",&l,&r);
q[++m]=make_pair(l,-i);
q[++m]=make_pair(r,i);
}
sort(q+1,q+m+1);
for(int i=1;i<=m;i++){
int j=q[i].second;
if(j>0){
if(st.size()==1)cnt[j]--;
st.erase(j);
}
else{
if(st.empty())cnt[-j]--;
st.insert(-j);
}
if(st.empty())ans++;
else if(st.size()==1)cnt[*st.begin()]++;
}
for(int i=1;i<=n;i++)mx=max(mx,cnt[i]),cnt[i]=0;
printf("%d\n",mx+ans);
return;
}
int main(){
scanf("%d",&T);
while(T--)solve();
return 0;
}
\(\text{T2}\) 打印机
栈是循环的。否则必定不优。那么可能的栈的类型共 \(6\) 种(ABC,ABC,BAC,BCA,CAB,CBA
)。设 \(f_{i,j,t}\) 表示当前第 \(i\) 位,\(j\) 为栈的大小,\(t\) 表示栈的类型。
dp 时根据栈内元素个数分类转移。
#include<bits/stdc++.h>
using namespace std;
const int mps[8][5]={{0,0,0,0},{0,1,2,3},{0,1,3,2},{0,2,1,3},{0,2,3,1},{0,3,1,2},{0,3,2,1}};
const int N=5e3+4;
const int inf=1e9;
int n,dp[2][N][8],a[N],ans=inf;
char s[N];
inline void Min(int &x,int y){
x=min(x,y);
return;
}
int main(){
scanf("%s",s+1);int len=strlen(s+1);
for(int i=1;i<=len;i++)if(s[i]!=s[i-1])a[++n]=s[i]-'A'+1;
memset(dp,0x3f,sizeof(dp));
dp[1][1][a[1]*2-1]=1;
for(int i=2;i<=n;i++){
int p=i&1,q=(i-1)&1;
for(int j=0;j<=i;j++)for(int k=1;k<=6;k++)dp[p][j][k]=inf;
for(int j=0;j<=i;j++){
for(int k=1;k<=6;k++){
// cout<<k<<" "<<a[i]<<" "<<i<<" "<<(j-1)%3+1<<"?\n";
if(a[i]!=mps[k][(j-1)%3+1])continue;
if(j==1){
if(!(k&1))continue;
Min(dp[p][j][k],dp[q][j-1][1]+1);
Min(dp[p][j][k],min(dp[q][j+1][k],dp[q][j+2][k]));
Min(dp[p][j][k],min(dp[q][j+1][k+1],dp[q][j+2][k+1]));
}
else if(j==2){
Min(dp[p][j][k],min(dp[q][j+1][k],dp[q][j+2][k]));
if(k&1)Min(dp[p][j][k],dp[q][j-1][k]+1);
else Min(dp[p][j][k],dp[q][j-1][k-1]+1);
}
else{
Min(dp[p][j][k],min(dp[q][j+1][k],dp[q][j+2][k]));
Min(dp[p][j][k],dp[q][j-1][k]+1);
}
// cout<<dp[p][j][k]<<" ";
}
}
}
for(int i=0;i<=n;i++)for(int k=1;k<=6;k++)ans=min(ans,dp[n&1][i][k]);
printf("%d",ans*2+len);
return 0;
}
\(\text{T3}\) 餐厅
同 CF1369E DeadLee。
考虑每个人都吃了两道菜。然后如果当前对于一个人还有剩下的那他肯定放哪里都没问题。所以扔到后面。
对于剩下的在放到最前面构成一个新的问题。注意此时要将之前扔到后面去的人吃掉的菜吐出来(bushi。如此做子问题即可。
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+6;
int n,m,a[N],head[N],tot,top,st[N];
bool vis[N];
queue<int>q;
struct edge{
int to,nxt,w;
}e[N<<1];
inline void addedge(int u,int v,int w){
e[++tot].to=v;
e[tot].nxt=head[u];
e[tot].w=w;
head[u]=tot;
return;
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)scanf("%d",&a[i]);
for(int i=1;i<=m;i++){
int x,y;
scanf("%d%d",&x,&y);
addedge(x,y,i),addedge(y,x,i);
a[x]--,a[y]--;
}
for(int i=1;i<=n;i++)if(a[i]>=0)q.push(i);
while(!q.empty()){
int u=q.front();
q.pop();
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].to;
if(vis[e[i].w])continue;
vis[e[i].w]=true;
st[++top]=e[i].w;
a[v]++;
if(!a[v])q.push(v);
}
}
if(top<m)puts("DEAD");
else{
puts("ALIVE");
while(top)printf("%d ",st[top--]);
}
return 0;
}
\(\text{T4}\) 战略游戏
同 P5332 [JSOI2019]精准预测。
一眼发现是 2-sat,发现暴力建图不可接受,考虑对于每个人只保留判定时间。然后图本身就是个 DAG,套路地使用 bitset 优化。
对于空间的限制,使用分块来计算结果。块长取 \(10^4\) 时优秀。
#include <bits/stdc++.h>
using namespace std;
const int N=1e5+6;
const int inf=1e9;
const int block=1e4;
const int M=N*3;
int nowt,n,k,ed[N],vis[M],head[M],ans[N],tot;
bitset<block>S,bs[M];
struct node{int op,t,x,y;}q[N];
map<int,int>mps[N];
struct edge{
int to,nxt;
}e[M<<1];
inline void addedge(int u,int v){
e[++tot].to=v;
e[tot].nxt=head[u];
head[u]=tot;
return;
}
inline void add(int u,int v,int op){
addedge(u+op,v),addedge(v+1,u+!op);
return;
}
inline void dfs(int u){
if(vis[u])return;
vis[u]=1;
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].to;
dfs(v);
bs[u]|=bs[v];
}
return;
}
int main(){
scanf("%d%d%d",&nowt,&n,&k);
for(int i=1;i<=k;i++){
scanf("%d%d%d%d",&q[i].op,&q[i].t,&q[i].x,&q[i].y);
mps[q[i].x][q[i].t]=i*2;
}
for(int i=1;i<=n;i++)ed[i]=mps[i][nowt+1]=(k+i)*2;
for(int i=1;i<=k;i++){
int u=mps[q[i].x][q[i].t],v=(*mps[q[i].y].lower_bound(q[i].t+!q[i].op)).second;
add(u,v,q[i].op);
}
for(int i=1;i<=n;i++){
int lst=0;
for(auto tmp:mps[i]){
int v=tmp.second;
if(lst)add(lst,v,0);
lst=v;
}
}
for(int l=1,r;l<=n;l+=block){
r=min(l+block-1,n);S.reset();
for(int i=2;i<=(n+k)*2+1;i++)vis[i]=0,bs[i].reset();
for(int i=l;i<=r;i++)bs[ed[i]][i-l]=1;
for(int i=2;i<=(n+k)*2+1;i++)dfs(i);
for(int i=l;i<=r;i++)if(bs[ed[i]+1][i-l])ans[i]=-inf,S[i-l]=1;
for(int i=1;i<=n;i++)ans[i]+=(r-l+1)-(S|bs[ed[i]+1]).count();
}
for(int i=1;i<=n;i++)printf("%d ",max(ans[i]-1,0));
return 0;
}
国庆联赛模拟赛DAY5
\(\text{T1}\) Box 不讲。
\(\text{T2}\) Replace
记 \(f_i\) 为考虑前 \(i\) 个数,第 \(i\) 个数不变的最小操作次数。
从 \(j(j>i)\) 能转移到 \(i\) 的充要条件是 \(a_i,a_j\) 之间的 \(b\)
数量 \(\ge j-i-1\)。
使用线段树维护。
#include<bits/stdc++.h>
using namespace std;
#define ls p<<1
#define rs p<<1|1
const int N=5e5+6;
const int inf=0x3f3f3f3f;
int n,m,a[N],b[N],pos[N],tr[N<<3],f[N],g[N];
inline void pushup(int p){
tr[p]=min(tr[ls],tr[rs]);
return;
}
inline void update(int p,int l,int r,int ps,int val){
if(l==r){
tr[p]=min(tr[p],val);
return;
}
int mid=(l+r)>>1;
if(ps<=mid)update(ls,l,mid,ps,val);
else update(rs,mid+1,r,ps,val);
pushup(p);
return;
}
inline int query(int p,int l,int r,int s,int t){
if(s<=l&&r<=t)return tr[p];
int mid=(l+r)>>1,res=inf;
if(s<=mid)res=min(res,query(ls,l,mid,s,t));
if(t>mid)res=min(res,query(rs,mid+1,r,s,t));
return res;
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)scanf("%d",&a[i]);
for(int i=1;i<=m;i++)scanf("%d",&b[i]);
sort(b+1,b+m+1);
a[n+1]=2*n+m-1,b[m+1]=inf;
for(int i=0;i<=n;i++)pos[i]=lower_bound(b+1,b+m+2,a[i])-b-1;pos[n+1]=m;
memset(f,0x3f,sizeof(f)),memset(g,0x3f,sizeof(g)),memset(tr,0x3f,sizeof(tr));
f[0]=0;
update(1,-n,m,1,0);
for(int i=1;i<=n;i++){
f[i]=min(f[i],g[i-1]);
g[i]=query(1,-n,m,-n,pos[i+1]-i+1)+i;
if(a[i]>a[i-1])f[i]=min(f[i],f[i-1]);
update(1,-n,m,1+pos[i]-i,f[i]-i);
}
if(min(f[n],g[n])==inf)puts("-1");
else printf("%d",min(f[n],g[n]));
return 0;
}
\(\text{T3}\) 苦难交易
同 CF1713E Cross Swapping。
从小到大枚举,对于 \(a_{x,y}\) 与 \(a_{y,x}\) 之间的关系容易推出,使用带权并查集维护。
#include<bits/stdc++.h>
using namespace std;
const int N=1e3+4;
int T,n,a[N][N],fa[N<<1];
inline int find(int x){
if(fa[x]==x)return x;
return fa[x]=find(fa[x]);
}
inline void merge(int x,int y){
x=find(x),y=find(y);
if(abs(x-y)%n)fa[x]=y;
return;
}
inline void solve(){
scanf("%d",&n);
for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)scanf("%d",&a[i][j]);
for(int i=1;i<=2*n;i++)fa[i]=i;
for(int i=1;i<=n;i++)for(int j=i+1;j<=n;j++){
if(a[i][j]<a[j][i])merge(i,j),merge(i+n,j+n);
else if(a[i][j]>a[j][i])merge(i,j+n),merge(i+n,j);
}
for(int i=1;i<=n;i++){
if(find(i)>n)continue;
for(int j=1;j<=n;j++)swap(a[i][j],a[j][i]);
}
for(int i=1;i<=n;i++,puts(""))for(int j=1;j<=n;j++)printf("%d ",a[i][j]);
return;
}
int main(){
scanf("%d",&T);
while(T--)solve();
return 0;
}
\(\text{T4}\) 线性代树
同 P3340 [ZJOI2014]星系调查。
考虑直接大力推式子。式子很容易推,最终结果是 \(S=\dfrac{C+A+\sqrt{(A-C)^2+B^2}}{2}\),那么维护 \(\sum x,\sum y,\sum x^2,\sum y^2,\sum xy\) 即可。
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+6;
int n,m,Q,S,T,X[N],Y[N],dep[N],sx[N],sy[N],sxx[N],syy[N],xy[N],top[N],tot,head[N],sz[N],son[N],fa[N];
double X_,Y_,XX,YY,XY,Dis;
struct edge{
int to,nxt;
}e[N<<1];
inline void addedge(int u,int v){
e[++tot].to=v;
e[tot].nxt=head[u];
head[u]=tot;
return;
}
inline void dfs1(int u,int faa){
// cout<<u<<" "<<faa<<"?\n";
sz[u]=1,fa[u]=faa,dep[u]=dep[faa]+1;
sx[u]=sx[faa]+X[u],sxx[u]=sxx[faa]+X[u]*X[u];
sy[u]=sy[faa]+Y[u],syy[u]=syy[faa]+Y[u]*Y[u];
xy[u]=xy[faa]+X[u]*Y[u];
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].to;
if(v==faa||sz[v]){
if(v&&v!=faa)S=u,T=v;
continue;
}
dfs1(v,u);
sz[u]+=sz[v];
if(sz[son[u]]<sz[v])son[u]=v;
}
return;
}
inline void dfs2(int u,int tp){
top[u]=tp;
if(son[u])dfs2(son[u],tp);
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].to;
if(top[v]||v==fa[u]||v==son[u]||(u==S&&v==T)||(u==T&&v==S))continue;
dfs2(v,v);
}
return;
}
inline int lca(int u,int v){
while(top[u]!=top[v]){
if(dep[top[u]]<dep[top[v]])swap(u,v);
u=fa[top[u]];
}
return dep[u]<dep[v]?u:v;
}
inline double dis(int u,int v){
int o=lca(u,v);
return dep[u]+dep[v]-dep[o]*2.0+1;
}
inline void query(int u,int v){
int o=lca(u,v);
X_+=sx[u]+sx[v]-sx[o]*2+X[o],XX+=sxx[u]+sxx[v]-2*sxx[o]+X[o]*X[o];
Y_+=sy[u]+sy[v]-sy[o]*2+Y[o],YY+=syy[u]+syy[v]-2*syy[o]+Y[o]*Y[o];
XY+=xy[u]+xy[v]-xy[o]*2+X[o]*Y[o];
Dis+=dis(u,v);
return;
}
inline double F(){
double D=1.0/Dis,u=XX-D*X_*X_,v=YY-D*Y_*Y_,w=XY-D*X_*Y_;
double res=(u+v)/2.0-sqrt(w*w+(u-v)/4.0*(u-v));
X_=XX=Y_=YY=XY=Dis=0;
return res;
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)scanf("%d%d",&X[i],&Y[i]);
for(int i=1;i<=m;i++){
int u,v;
scanf("%d%d",&u,&v);
addedge(u,v),addedge(v,u);
}
dfs1(1,0);
// puts("!");
dfs2(1,1);
scanf("%d",&Q);
while(Q--){
int u,v,o;double ans;
scanf("%d%d",&u,&v),o=lca(u,v);
query(u,v);
ans=F();
if(n==m){
query(u,S),query(v,T);
ans=min(ans,F());
query(u,T),query(v,S);
ans=min(ans,F());
}
printf("%.6lf\n",ans);
}
return 0;
}
国庆联赛模拟赛DAY6
咕了。
标签:return,int,res,国庆,ans,inline,合集,集训,mod From: https://www.cnblogs.com/trsins/p/17970749