Game
意思就是给一个含有n个数的排列,每次取两个数(只剩一个就不取了),如果两个数互质加一分,求分数的期望值。
期望的线性性
E(x+y)=E(x)+E(y)
主要作用于条件概率,比如有n个物品,其中有特定的物品m个,在x次抽取的过程中,抽取到一次特定物品就得到y分,求x次抽取得分的期望
期望分数等于=(第一次抽取时抽到特定物品的得分期望)*x
例如:
52张牌,抽取10次,抽到‘A’得分为2,得分期望就是 1/13210
#include <iostream>
using namespace std;
int gcd(int a,int b)
{
return b==0?a:gcd(b,a%b);
}
int main()
{
int n;
cin>>n;
int cnt=0;
for(int i=1;i<=n;i++)
for(int j=i+1;j<=n;j++)
if(gcd(i,j)==1)
cnt++;
if(n&1)
{
int temp=gcd(n,cnt);
temp=max(temp,1);
cout<<cnt/temp<<"/"<<(n)/temp<<endl;
}
else
{
int temp=gcd(n-1,cnt);
temp=max(temp,1);
cout<<cnt/temp<<"/"<<(n-1)/temp<<endl;
}
return 0;
}
P1637 三元上升子序列
给一个数组求a[i]<a[j]<a[k](i<j<k)的方案数
自然能想到的是方案数就是对于每个位置j来讲,我们令所有前方小于j位置的数*所有后方大于j位置的数
那么问题的关键就在于如何确定每个位置的个数。这里用权值线段树来求,我们通过每次从左到右插入一个数之前,查询整个线段树里所有小于目前即将插入的这个数的个数,这样就能保证i<j。同理可以逆序插入求的大于目前数的个数并且保证j<k
#include<bits/stdc++.h>
#define ls rt<<1
#define rs rt<<1|1
#define mid ((l+r)>>1)
#define int long long
using namespace std;
const int N = 1e7+100;
struct node
{
int num,pos;
};
bool cmp(const node &a,const node &b)
{
return a.num<b.num;
}
node a[N];
int num[N],cnt,tree[N<<2],small[N],big[N];
int ans;
void up(int rt)
{
tree[rt]=tree[ls]+tree[rs];
}
void fix(int rt,int l,int r,int num)
{
if(l==r&&l==num)
{
tree[rt]++;
return;
}
if(num<=mid)
fix(ls,l,mid,num);
else
fix(rs,mid+1,r,num);
up(rt);
}
int query(int rt,int l,int r,int nl,int nr)
{
if(nl<=l&&r<=nr)
return tree[rt];
int res=0;
if(nl<=mid)
res+=query(ls,l,mid,nl,nr);
if(nr>mid)
res+=query(rs,mid+1,r,nl,nr);
return res;
}
signed main()
{
int n;
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>a[i].num;
a[i].pos=i;
}
sort(a+1,a+n+1,cmp);
for(int i=1;i<=n;i++)
{
if(a[i].num>a[i-1].num)
cnt++;
num[a[i].pos]=cnt;
}
for(int i=1;i<=n;i++)
{
if(num[i]>1)
small[i]=query(1,1,n,1,num[i]-1);
fix(1,1,n,num[i]);
}
memset(tree,0,sizeof(tree));
for(int i=n;i>=1;i--)
{
if(num[i]<n)
big[i]=query(1,1,n,num[i]+1,n);
fix(1,1,n,num[i]);
}
for(int i=1;i<=n;i++)
{
ans+=small[i]*big[i];
}
cout<<ans<<endl;
return 0;
}
Circulant Matrix
题目大意:
给定a,b数组,和如下关系,求x数组
The element A[i][j] satisfies A[i][j] = a[i xor j],
A[0][0]*x[0] + A[0][1]*x[1] + A[0][2]*x[2] + A[0][3]*x[3] = b[0] (mod p)
A[1][0]*x[0] + A[1][1]*x[1] + A[1][2]*x[2] + A[1][3]*x[3] = b[1] (mod p)
A[2][0]*x[0] + A[2][1]*x[1] + A[2][2]*x[2] + A[2][3]*x[3] = b[2] (mod p)
A[3][0]*x[0] + A[3][1]*x[1] + A[3][2]*x[2] + A[3][3]*x[3] = b[3] (mod p)
显然能看到,A数组是由a数组对应异或下标位置得来,
易得出x数组是b和1/a的卷积得来,利用fwt加速卷积过程即可。
#include<iostream>
#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<stack>
#define inf 1e18
using namespace std;
int n;
const long long p = 1e9 + 7;
const int maxn = 263000;
long long a[maxn], b[maxn];
long long qpow(long long a, long long b)
{
long long ans = 1;
while (b) {
if (b & 1) {
ans = ans * a % p;
}
a = a * a % p;
b = b >> 1;
}
return ans;
}
void fwt(long long a[])
{
for (int d = 1; d < n; d <<= 1) {
for (int m = d << 1, i = 0; i < n; i += m) {
for (int j = 0; j < d; j++) {
long long x = a[i + j], y = a[i + j + d];
a[i + j] = (x + y) % p;
a[i + j + d] = (x - y + p) % p;
}
}
}
}
void ifwt(long long a[])
{
long long inv = qpow(2, p - 2);//并不知道为什么是p - 2
for (int d = 1; d < n; d <<= 1) {
for (int m = d << 1, i = 0; i < n; i += m) {
for (int j = 0; j < d; j++) {
long long x = a[i + j], y = a[i + j + d];
a[i + j] = (x + y) % p;
a[i + j + d] = (x - y + p) % p;
a[i + j] = a[i + j] * inv % p;
a[i + j + d] = a[i + j + d] * inv % p;
}
}
}
}
int main()
{
while (scanf("%d", &n) != EOF) {
for (int i = 0; i < n; i++) {
scanf("%d", &a[i]);
}
for (int i = 0; i < n; i++) {
scanf("%d", &b[i]);
}
fwt(a);
fwt(b);
for (int i = 0; i < n; i++) {
a[i] = (b[i] * qpow(a[i], p - 2)) % p;
}
ifwt(a);
for (int i = 0; i < n; i++) {
cout << a[i] << endl;
}
}
}
P4717 【模板】快速莫比乌斯/沃尔什变换 (FMT/FWT)
一个将FWT的and,or,xor运算都包含的题目。挺好的,一下把板子搞全了
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int mod =998244353;
int n;
const int N = 1<<17|1;
int a[N];
int b[N];
int A[N],B[N];
void in()
{
for(int i=0;i<n;i++)
a[i]=A[i];
for(int i=0;i<n;i++)
b[i]=B[i];
}
void get()
{
for(int i=0;i<n;i++)
a[i]*=b[i];
}
int fastpow(int n,int a)
{
int res=1;
while(n)
{
if(n&1)
res=res*a%mod;
n>>=1;
a=a*a%mod;
}
return res%mod;
}
void FWT_or(int *a,int op)
{
for(int i=1;i<n;i<<=1)
{
for(int p=i<<1,j=0;j<n;j+=p)
{
for(int k=0;k<i;k++)
{
(a[i+j+k]+=a[j+k]*op+mod)%=mod;
}
}
}
}
void FWT_and(int *a,int op)
{
for(int i=1;i<n;i<<=1)
{
for(int p=i<<1,j=0;j<n;j+=p)
{
for(int k=0;k<i;k++)
{
(a[j+k]+=a[i+j+k]*op+mod)%=mod;
}
}
}
}
void FWT_xor(int *a,int op)
{
int inv2=fastpow(mod-2,2);
for(int i=1;i<n;i<<=1)
{
for(int p=i<<1,j=0;j<n;j+=p)
{
for(int k=0;k<i;k++)
{
int X=a[j+k],Y=a[i+j+k];
a[j+k]=(X+Y)%mod;
a[i+j+k]=(X-Y+mod)%mod;
if(op==-1)
(a[j+k]*=inv2)%=mod,(a[i+j+k]*=inv2)%=mod;
}
}
}
}
void out()
{
for(int i=0;i<n;i++)
cout<<(a[i]+mod)%mod<<" ";
cout<<endl;
}
signed main()
{
cin>>n;
int temp=1;
temp<<=n;
n=temp;
for(int i=0;i<n;i++)
cin>>A[i];
for(int i=0;i<n;i++)
cin>>B[i];
in();FWT_or(a,1);FWT_or(b,1);get();FWT_or(a,-1);out();
in();FWT_and(a,1);FWT_and(b,1);get();FWT_and(a,-1);out();
in();FWT_xor(a,1);FWT_xor(b,1);get();FWT_xor(a,-1);out();
}
明天开刷后缀数组的题啦,今天吧LCP的性质/求法和SA的定义/求法看了看,写个模板题
SA【i】:排名为i的后缀的第一个字符在原串中的下标
rk[i]:起始字母下表在原串中为i的后缀的排名。
suff(j):以原串第j个字母为首字母的后缀
LCP(i,j):suff(sa[i])与suff(sa[j])的最长公共前缀(自己绕一下)
LCP的四个性质
1.LCP(i,j)=LCP(j,i);
2.LCP(i,i)=len(sa[i])=n-sa[i]+1;
以上两条十分自然,第一条是显然的,第二条就是求了个排名为i的后缀长度
**
3.LCP(i,k)=min(LCP(i,j),LCP(j,k)) 对于任意1<=i<=j<=k<=n
4.LCP(i,k)=min(LCP(j,j-1)) 对于任意1<i<=j<=k<=n
**
复杂度分析:如果使用快排,复杂度是n(logn)2,这里使用基数排序,复杂度为nlogn
#include <bits/stdc++.h>
#define ini inline int
#define maxn 1000050
using namespace std;
char s[maxn];
int y[maxn],x[maxn],c[maxn],sa[maxn],rk[maxn],height[maxn],wt[30];
int n,m;
void get_SA()
{
for (int i=1;i<=n;++i)
{
x[i]=s[i];
++c[x[i]];
//c数组是桶
//x[i]是第i个元素的第一关键字
}
for (int i=2;i<=m;++i)
c[i]+=c[i-1];
//做c的前缀和,我们就可以得出每个关键字最多是在第几名
for(int i=n;i>=1;--i)
sa[c[x[i]]--]=i;
for (int k=1;k<=n;k<<=1)
{
int num=0;
for(int i=n-k+1;i<=n;++i)
y[++num]=i;
//y[i]表示第二关键字排名为i的数,第一关键字的位置
//第n-k+1到第n位是没有第二关键字的 所以排名在最前面
for (int i=1;i<=n;++i)
if (sa[i]>k)
y[++num]=sa[i]-k;
//排名为i的数 在数组中是否在第k位以后
//如果满足(sa[i]>k) 那么它可以作为别人的第二关键字,就把它的第一关键字的位置添加进y就行了
//所以i枚举的是第二关键字的排名,第二关键字靠前的先入队
for (int i=1;i<=m;++i)
c[i]=0;
//初始化c桶
for (int i=1;i<=n;++i)
++c[x[i]];
//因为上一次循环已经算出了这次的第一关键字 所以直接加就行了
for (int i=2;i<=m;++i)
c[i]+=c[i-1];//第一关键字排名为1~i的数有多少个
for (int i=n;i>=1;--i)
sa[c[x[y[i]]]--]=y[i],y[i]=0;
swap(x,y);
x[sa[1]]=1;num=1;
for (int i=2;i<=n;++i)
x[sa[i]]=(y[sa[i]]==y[sa[i-1]] && y[sa[i]+k]==y[sa[i-1]+k]) ? num : ++num;
//因为sa[i]已经排好序了,所以可以按排名枚举,生成下一次的第一关键字
if (num==n) break;
m=num;
//这里就不用那个122了,因为都有新的编号了
}
for(int i=1;i<=n;++i)
{
cout<<sa[i]<<" ";
}
}
void get_height()
{
int k=0;
for (int i=1;i<=n;++i)
rk[sa[i]]=i;
for (int i=1;i<=n;++i)
{
if (rk[i]==1) continue;//第一名height为0
if (k) --k;//h[i]>=h[i-1]+1;
int j=sa[rk[i]-1];
while (j+k<=n && i+k<=n && s[i+k]==s[j+k]) ++k;
height[rk[i]]=k;//h[i]=height[rk[i]];
}
}
int main()
{
gets(s+1);
n=strlen(s+1);m=122;
get_SA();
}
CF1659C
题意:给出一个n*m大小的1,-1矩阵,从左上角走到右下角,能否有种方法让所有步踩到的地砖上数字的和为0
想法和做法都很常规,首先最大值小于0和最小值大于0肯定pass掉,其次更换路线最少会让原来的总和变化:0,2,-2三种情况。所以在最大值大于0并且最小值小于0的情况下,如果最大值不能被2整除还是不能够打成条件。
最大值和最小值一眼dp维护没什么好说的。
#include <iostream>
using namespace std;
const int N = 1e3+100;
int grid[N][N],mn[N][N],mx[N][N];
int main()
{
int t,n,m;
for(cin>>t;t;t--)
{
cin>>n>>m;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
cin>>grid[i][j];
}
}
mn[1][1]=mx[1][1]=grid[1][1];
for(int i=2;i<=n;i++)
mx[i][1]=mn[i][1]=mx[i-1][1]+grid[i][1];
for(int j=2;j<=m;j++)
mx[1][j]=mn[1][j]=mx[1][j-1]+grid[1][j];
for(int i=2;i<=n;i++)
{
for(int j=2;j<=m;j++)
{
mx[i][j]=max(mx[i-1][j],mx[i][j-1])+grid[i][j];
mn[i][j]=min(mn[i-1][j],mn[i][j-1])+grid[i][j];
}
}
if(mx[n][m]<0||mn[n][m]>0||mx[n][m]%2)
{
cout<<"NO"<<endl;
}
else
{
cout<<"YES"<<endl;
}
}
return 0;
}