循环位移
- 哈希,已知开头和长度,利用哈希相减得出该段是否为一个A串的循环
#include <bits/stdc++.h>
#pragma GCC optimize(2)
using namespace std;
using i64 = long long;
using pii = pair<int,int>;
const int m1 = 1e9 + 7, m2 = 1e9 + 9;
struct Hash{
i64 x, y;
Hash(): x(0), y(0) {}
Hash(i64 x, i64 y): x(x), y(y) {}
Hash(char x, char y): x(x), y(y) {}
bool operator < (const Hash &t) const{
return x < t.x || (x == t.x && y < t.y);
}
bool operator == (const Hash &t) const{
return x == t.x && y == t.y;
}
bool operator != (const Hash &t) const{
return x != t.x || y != t.y;
}
};
Hash operator + (const Hash & a, const Hash &b){
return Hash((a.x + b.x) % m1, (a.y + b.y) % m2);
}
Hash operator - (const Hash &a, const Hash &b){
return Hash((a.x - b.x + m1) % m1, (a.y - b.y + m2) % m2);
}
Hash operator * (const Hash &a, const Hash &b){
return Hash(a.x * b.x % m1, a.y * b.y % m2);
}
Hash base;
int main(){
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int T;
//T=1;
std::cin>>T;
for(;T>0;--T){
std::srand(time(0));
base = Hash(1LL * rand() % m1, 1LL * rand() % m2 );
string a;std::cin>>a;
string b;std::cin>>b;
std::vector<Hash> bs(b.length()+5),pw(b.length()+5);
pw[0] = Hash(1LL, 1LL);
for(int i=1;i<=b.length();++i){
pw[i]=pw[i-1]*base;
bs[i]=bs[i-1]*base+Hash(1LL*b[i-1],1LL*b[i-1]);
}
Hash as;
std::set<Hash> hs;
for(int i=0,l=0;i<2*a.length()-1;++i){
if(i>=a.length()){
as=as-Hash(1LL*a[l],1LL*a[l])*pw[a.length()-1];
as=as*base+Hash(1LL*a[l],1LL*a[l]);
l++;
hs.insert(as);
}else {
as=as*base+Hash(1LL*a[i],1LL*a[i]);
if(i==a.length()-1)hs.insert(as);
}
// std::cout<<as.x<<" "<<as.y<<std::endl;
}
auto get=[&](int l,int r){
return bs[r]-bs[l-1]*pw[r-l+1];
};
int ans=0;
for(int i=1;i<=b.length();++i){
if(i+a.length()-1>b.length())break;
Hash check=get(i,i+a.length()-1);
if(hs.contains(check))ans++;
}
std::cout<<ans<<"\n";
}
return 0;
}
树
- 根据题意,若向一个集合中加入一个新点
- 对于大于点值的为mx*(mx-v)=mx^2-mx*v
- 对于小于点值的为v*(v-mi)=v^2-v*mi
- 总贡献为Sum{mx^2}+v^2*Num(mi)-v*Sum{集合}+v^2*Num(v)
- 即Sum{u^2(u>=v)}+v^2*Num(mi)-v*Sum{集合}
- 找出大于等于v的数平方和,集合的大小,小于v的数的个数,用值域线段树维护
- 往集合中加入,考虑dsu on tree
- 每次只将轻儿子加入集合中
- 轻儿子自己计算完后,撤销在线段树上的记录
- 重儿子不撤销记录,访问到轻儿子时,把轻儿子及其子树全一个个加入
- 由于轻儿子最多访问logn次,复杂度保证O(nlog^2)
- 注意模数为2^64,要开__int128
#include <bits/stdc++.h>
using namespace std;
using i64 = long long;
using pii = pair<int,int>;
struct Tr{
i64 sum1,sum2;int cnt;
Tr(): sum1(0),sum2(0),cnt(0) {}
Tr(i64 x,i64 y,int z): sum1(x),sum2(y),cnt(z) {}
};
Tr operator + (const Tr &a,const Tr &b){
return Tr(a.sum1+b.sum1,a.sum2+b.sum2,a.cnt+b.cnt);
}
struct Tree{
std::vector<Tr> tr;
int siz;
#define ls p<<1
#define rs p<<1|1
void init(int n){
siz=n*6;
tr.resize(siz);
}
void pushup(int p){
tr[p]=tr[ls]+tr[rs];
}
void build(int rt, int l, int r){
if (l == r){
// 初始化信息
return;
}
int m = (l + r) >> 1;
build(rt << 1, l, m);
build(rt << 1 | 1, m+1, r);
pushup(rt);
}
void update(int p,int l,int r,int k,const Tr &x){
if(l==r){
tr[p]=tr[p]+x;
return;
}
int mid=(l+r)>>1;
if(k<=mid)update(ls,l,mid,k,x);
else update(rs,mid+1,r,k,x);
pushup(p);
}
Tr query(int p,int l,int r,int ql,int qr){
if(ql==l&&r==qr){
return tr[p];
}
int mid=(l+r)>>1;
if(qr<=mid)return query(ls,l,mid,ql,qr);
else if(ql>mid)return query(rs,mid+1,r,ql,qr);
else return query(ls,l,mid,ql,mid)+ query(rs,mid+1,r,mid+1,qr);
}
Tr query(int ql,int qr){
if(ql>qr)return Tr();
return query(1,1,1e6,ql,qr);
}
};
inline void read(__int128 &n){
__int128 x=0,f=1;
char ch=getchar();
while(ch<'0'||ch>'9'){
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9'){
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
n=x*f;
}
inline void print(__int128 n){
if(n<0){
putchar('-');
n*=-1;
}
if(n>9) print(n/10);
putchar(n % 10 + '0');
}
int main(){
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
// long long md=(long long)998244353;
// i64 mx=LONG_LONG_MAX/2;
Tree tree;
tree.init(1e6);
tree.build(1,1,1e6);
int n;std::cin>>n;
std::vector<std::vector<int>> G(n+5);
for(int i=1;i<n;++i){
int u,v;std::cin>>u>>v;
G[u].push_back(v);
G[v].push_back(u);
}
std::vector<int> a(n+5);
for(int i=1;i<=n;++i)std::cin>>a[i];
std::vector<int> son(n+5,0),sz(n+5,0);
auto dfs1=[&](auto self,int x,int fa)->void{
sz[x]=1;
for(auto v:G[x]){
if(v==fa)continue;
self(self,v,x);
sz[x]+=sz[v];
if(!son[x]||sz[son[x]]<sz[v])son[x]=v;
}
};
__int128 res=0;
__int128 base=1;
__int128 ans=0;
__int128 md=1;
for(int i=1;i<=64;++i)md=md*2;md=md-1;
auto mk=[&](int x,int op){
if(op){
Tr u=tree.query(a[x]+1,1e6);
Tr v=tree.query(1,a[x]);
Tr w=tree.query(1,1e6);
__int128 ress=base*u.sum2+base*a[x]*a[x]*v.cnt-base*w.sum1*a[x];
res+=ress;
tree.update(1,1,1e6,a[x],Tr(a[x],1LL*a[x]*a[x],1));
}else{
tree.update(1,1,1e6,a[x],Tr(-a[x],-1LL*a[x]*a[x],-1));
Tr u=tree.query(a[x]+1,1e6);
Tr v=tree.query(1,a[x]);
Tr w=tree.query(1,1e6);
__int128 ress=base*u.sum2+base*a[x]*a[x]*v.cnt-base*w.sum1*a[x];
res-=ress;
}
};
auto calc=[&](auto self,int x,int fa,int op)->void{
mk(x,op);
for(auto v:G[x]){
if(v!=fa)self(self,v,x,op);
}
};
auto dfs2=[&](auto self,int x,int fa)->void{
for(auto v:G[x]){
if(v==son[x]||v==fa)continue;
self(self,v,x);
calc(calc,v,x,0);
}
if(son[x])self(self,son[x],x);
for(auto v:G[x]){
if(v==fa||v==son[x])continue;
calc(calc,v,x,1);
}
mk(x,1);
ans=ans&md;
ans^=(res*2);
};
dfs1(dfs1,1,0);
dfs2(dfs2,1,0);
ans=ans&md;
print(ans);
}
博弈
- 考虑n种颜色全为偶数个
- 对于一种颜色,其个数为偶数t
- 选取方案数为t!
- 可以分为t/2组
- 对于已经分好组的各个颜色
- 共进行Sum/2=m轮
- 则分好的组往里放的方案数为
- C(m,t1)*C(m-t1,t2)*C(m-t1-t2,t3)……=》
- m!*inv[t1]*inv[m-t1]*(m-t1)!*inv[t2]*inv[m-t1-t2]*…… =》
- m!*inv[t1]*inv[t2]*inv[3]……
- 所以平局数为h1!*h2!*h3!*……*(Sum/2)!*inv[h1/2]*inv[h2/2]*inv[h3/2]……
- 总方案数为Sum!
- 所以平局的概率为h1!*h2!*h3!*……*(Sum/2)!*inv[h1/2]*inv[h2/2]*inv[h3/2]……*inv[Sum]
- 记作p
- 所以A赢的概率为(i-p)/2
- 现考虑有一种颜色为奇数个
- 先考虑将多的一个丢去
- 平局概率依旧为p,A赢为(1-p)/2
- 此时在平局后A再拿多的那个,A变为了赢
- 所以A赢概率为(1-p)/2+p
- 再考虑有多个奇数的
- 此时AB一定是能分出胜负的(等于个数全为1个)
- 且胜负可以调换,概率一定为1/2
#include <bits/stdc++.h>
#pragma GCC optimize(2)
using namespace std;
using i64 = long long;
using pii = pair<int,int>;
int main(){
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
i64 md=998244353;
int N=1e7;
std::vector<i64> fac(N+5);
fac[0]=fac[1]=1;
for(int i=2;i<=N;++i){
fac[i]=fac[i-1]*i%md;
}
auto pow=[&](i64 a,i64 b){
i64 res=1;
while(b>0){
if(b&1)res=res*a%md;
a=a*a%md;
b>>=1;
}
return res;
};
i64 two=pow(2,md-2);
int T;
// T=1;
std::cin>>T;
for(;T>0;--T){
int n;std::cin>>n;
std::vector<int> h(n+5);
int sum=0;
int odd=0;
for(int i=1;i<=n;++i){
char x;std::cin>>x;
std::cin>>h[i];
if(h[i]&1)odd++;
sum+=h[i];
}
if(odd>1){
std::cout<<two<<std::endl;
continue;
}
i64 p=1;
i64 b=1;
for(int i=1;i<=n;++i){
p*=fac[h[i]]%md;p%=md;
}
b*=fac[sum]%md;b%=md;sum=0;
for(int i=1;i<=n;++i){
h[i]/=2;
sum+=h[i];
b*=fac[h[i]]%md;b%=md;
}
p*=fac[sum]%md;p%=md;
p*=pow(b,md-2)%md;p%=md;
// p*=fac[sum/2]%md*inv[sum]%md;
if(odd){
std::cout<<((md+1-p)%md*two%md+p)%md<<"\n";
}else std::cout<<(md+1-p)%md*two%md<<"\n";
}
return 0;
}
序列平方
- 若一种子序列有k个
- 则答案为K*k*k
- 从所有子序列中选出该种序列,有k种
- 独立的选3次,则组合起来有k*k*k种
- 定义 f i j k
- 表示第一次选的序列结尾为i,第二次选的结尾为j,第三次为k
- 此时3个序列相同的方案数
- 初始f[0][0][0]=1
- 考虑到子序列不连续
- 若当前ai=aj=ak,则可以由之前的任意一组相同的一步转移
- 暴力枚举达到n^6
- 考虑高维前缀和
- 容斥,前一维的-前两维的+前三维的
#include <bits/stdc++.h>
#pragma GCC optimize(2)
using namespace std;
using i64 = long long;
using pii = pair<int,int>;
const i64 md=998244353;
void add(i64 &a,const i64 &b){
a=(a+b)%md;
}
void del(i64 &a,const i64 &b){
a=(md+a-b)%md;
}
int main(){
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int T;
T=1;
// std::cin>>T;
for(;T>0;--T){
int n;std::cin>>n;
std::vector<int> a(n+5);
for(int i=1;i<=n;++i)std::cin>>a[i];
std::vector<std::vector<std::vector<i64>>> f(n+5,std::vector<std::vector<i64>>(n+5,std::vector<i64>(n+5,0LL)));
f[0][0][0]=1;//直接跳到长度为1的相同序列
i64 ans=0;
for(int i=1;i<=n;++i){
for(int j=1;j<=n;++j){
for(int k=1;k<=n;++k){
add(f[i][j][k],f[i-1][j][k]);
add(f[i][j][k],f[i][j-1][k]);
add(f[i][j][k],f[i][j][k-1]);
del(f[i][j][k],f[i-1][j-1][k]);
del(f[i][j][k],f[i][j-1][k-1]);
del(f[i][j][k],f[i-1][j][k-1]);
add(f[i][j][k],f[i-1][j-1][k-1]);
if(a[i]==a[j]&&a[i]==a[k]){
add(ans,f[i][j][k]);
add(f[i+1][j+1][k+1],f[i][j][k]);//出现新的跳板
}
}
}
}
std::cout<<ans<<std::endl;
}
return 0;
}
并
- 一个矩阵有多个小正方形组成
- 考虑小正方形的贡献
- 若该小正方形被i个矩阵覆盖
- 想要消去其对答案的影响
- 则需要不选择覆盖其的i个矩阵
- 所以答案里有该小正方形的概率为 1-C(n-i,k)/C(n,k) (总-不选i个矩阵的任意一个))
- 由于x,y较大,考虑离散化
- 对于计算每个小正方形被多少个矩阵覆盖
- 用二维前缀和
- 给的是点坐标,要统计小正方形
- 转为格点坐标计算二维前缀和,x++,y++
- 例读入为点坐标,左上角为(1,1),即该格为(2,2)
#include <bits/stdc++.h>
#pragma GCC optimize(2)
using namespace std;
using i64 = long long;
using pii = pair<int,int>;
int main(){
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
const i64 md=998244353;
const int N=4e3;
std::vector<i64> fac(N+5),inv(N+5),f0(N+5);//md为奇数
fac[1]=inv[1]=f0[1]=fac[0]=inv[0]=f0[0]=1;
for(int i=2;i<=N;++i){
fac[i]=fac[i-1]*i%md;
f0[i]=(md-md/i)*f0[md%i]%md;
inv[i]=inv[i-1]*f0[i]%md;
}
auto Cal=[&](i64 n,i64 m){
if(n<0||m<0||n<m)return 0LL;
return fac[n]*inv[m]%md*inv[n-m]%md;
};
int T;
T=1;
// std::cin>>T;
for(;T>0;--T){
struct M{
i64 x,y,x2,y2;
};
int n;std::cin>>n;
std::vector<M> ku(n+5);
std::vector<i64> a,b;
for(int i=1;i<=n;++i){
std::cin>>ku[i].x>>ku[i].y>>ku[i].x2>>ku[i].y2;
a.push_back(ku[i].x);a.push_back(ku[i].x2);
b.push_back(ku[i].y);b.push_back(ku[i].y2);
}
sort(a.begin(),a.end());
sort(b.begin(),b.end());
std::vector<i64> x(a.size()+5),y(b.size()+5);
std::map<i64,int> mpx,mpy;
int cntx=0,cnty=0;
for(int i=0;i<a.size();++i){
if(i==0) x[++cntx]=a[i];
else if(a[i]!=a[i-1])x[++cntx]=a[i];
}
for(int i=1;i<=cntx;++i){
mpx[x[i]]=i;
}
for(int i=0;i<b.size();++i){
if(i==0)y[++cnty]=b[i];
else if(b[i]!=b[i-1])y[++cnty]=b[i];
}
for(int i=1;i<=cnty;++i){
mpy[y[i]]=i;
}
std::vector<std::vector<int>> sum(cntx+5,std::vector<int>(cnty+5,0));
auto mk=[&](M now){
auto[x1,y1,x2,y2]=now;
x1=mpx[x1];x2=mpx[x2];
y1=mpy[y1];y2=mpy[y2];
x1++;y1++;
sum[x1][y1]++;
sum[x1][y2+1]--;
sum[x2+1][y1]--;
sum[x2+1][y2+1]++;
};
for(int i=1;i<=n;++i){
mk(ku[i]);
}
std::vector<i64> tot(n+5,0);
for(int i=1;i<=cntx;++i){
for(int j=1;j<=cnty;++j){
i64 tmp=(sum[i-1][j]+sum[i][j-1])%md;
tmp-=sum[i-1][j-1];tmp%=md;
sum[i][j]+=tmp;sum[i][j]%=md;
tot[sum[i][j]]+=1LL*(x[i]-x[i-1])*(y[j]-y[j-1])%md;
tot[sum[i][j]]%=md;
}
}
auto pow=[&](i64 a,i64 b){
i64 res=1;
while(b>0){
if(b&1)res=res*a%md;
a=a*a%md;
b>>=1;
}
return res;
};
std::vector<i64> ans(n+5,0);
for(int k=1;k<=n;++k){
i64 p=pow(Cal(n,k),md-2);
for(int i=1;i<=n;++i){
ans[k]+=tot[i]*(md+1-Cal(n-i,k)*p%md)%md;
ans[k]%=md;
}
}
for(int i=1;i<=n;++i)std::cout<<ans[i]<<"\n";
}
return 0;
}
三元环
- 有向图,任意两个点之间有唯一一条确认方向的边
- 所以生成图为竞赛图
- 有向三元环,环上三点之间的边,加上后,每个点的入度为1
- 答案为总方案-不合法
- 总方案为C(n,3)
- 对于一个点,若其在图中入度大于等于2
- 选择两个指向其的点,可以构成一个不合法的方案
- 所以不合法的方案为Sum i { C(in[i],2) }
- 由于点数很多,不能枚举建边
- 又因为i->j有边,满足i < j&&fi < fj&&gi < gj
- 所以考虑三维偏序
- 对于一点i,指向其的边个数为
- 加:
i<j && (fj <= fi ||gk <= gi ) 即i<-j,solve1
j<i && (fj < fi &&gj < gi ) 即j->i,solve2 - 减:
i<j && (fj <= fi &&gk <= gi ) 即i<-j,solve3 - 对于三维偏序
- 先将i排序
- 对于第二维做归并排序,CDQ分治
- 对于(l,mid),(mid+1,r),其中第二维单调不降
- (l,mid)中fi,(mid+1,r)中fj
- fi fj满足偏序关系,则 gi入树状数组,可能对j有贡献,i往后走
- fi fj不满足,则i往后都不满足,j统计树状数组中g满足的个数,j往后走
- 注意清空树状数组,回退一遍标记即可
- (f,g,id)不会有重的,不用考虑去重
#include <bits/stdc++.h>
using namespace std;
using i64 = long long;
using pii = pair<int,int>;
const i64 md=998244353;
struct Tr{
int siz;
std::vector<i64> tr;
void init(int n){
siz=n;
tr.assign(n+5,0);
}
#define lowbit(x) (x&-x)
void update(int x,int k){
for(int i=x;i<=siz;i+=lowbit(i)){
tr[i]+=k;
}
}
int query(int x){
i64 res=0;
for(int i=x;i;i-=lowbit(i)){
res+=tr[i];
}
return res;
}
int query(int l,int r){
if(r>l)return 0;
else return query(r)-query(l-1);
}
};
int main(){
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int n;std::cin>>n;
std::vector<int> f(n+5),g(n+5),in(n+5,0);
Tr tree;tree.init(n+5);
for(int i=1;i<=n;++i)std::cin>>f[i];
for(int i=1;i<=n;++i)std::cin>>g[i];
auto solve1=[&](){
for(int i=n;i>=1;--i)in[i]+=tree.query(f[i]),tree.update(f[i],1);
for(int i=n;i>=1;--i)tree.update(f[i],-1);
for(int i=n;i>=1;--i)in[i]+=tree.query(g[i]),tree.update(g[i],1);
for(int i=n;i>=1;--i)tree.update(g[i],-1);
};
solve1();
struct M{
int f,g,id;
bool operator<(const M&other)const{
return f<other.f;
}
};
std::vector<M> p(n+5);
auto solve2=[&](auto self,int l,int r)->void{
if(l>=r)return;
int mid=(l+r)>>1;
self(self,l,mid);self(self,mid+1,r);
sort(p.begin()+l,p.begin()+mid+1);
sort(p.begin()+mid+1,p.begin()+r+1);
int i,j;
for(i=l,j=mid+1;j<=r;++j){
while(i<=mid&&p[i].f<p[j].f)tree.update(p[i++].g,1);
in[p[j].id]+=tree.query(p[j].g-1);
}
//用了(l,i-1)
for(j=l;j<i;++j)tree.update(p[j].g,-1);
};
auto solve3=[&](auto self,int l,int r)->void{
if(l>=r)return;
int mid=(l+r)>>1;
self(self,l,mid);self(self,mid+1,r);
sort(p.begin()+l,p.begin()+mid+1);
sort(p.begin()+mid+1,p.begin()+r+1);
int i,j;
for(i=l,j=mid+1;i<=mid;++i){
while(j<=r&&p[j].f<=p[i].f)tree.update(p[j++].g,1);
in[p[i].id]-=tree.query(p[i].g);
}
for(i=mid+1;i<j;++i)tree.update(p[i].g,-1);
};
for(int i=1;i<=n;++i)p[i].f=f[i],p[i].g=g[i],p[i].id=i;
solve2(solve2,1,n);
for(int i=1;i<=n;++i)p[i].f=f[i],p[i].g=g[i],p[i].id=i;
solve3(solve3,1,n);
i64 ans=1LL*n*(n-1)*(n-2)/6LL;
for(int i=1;i<=n;++i)ans-=1LL*in[i]*(in[i]-1)/2LL;
std::cout<<ans<<std::endl;
return 0;
}