(题目练习)条件概率+权值线段树+FWT+后缀数组

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();
}

P3809 【模板】后缀排序

明天开刷后缀数组的题啦,今天吧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;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值