06.01个人训练赛_A求和(模拟) B相似(思维,组合数学)

						A求和	
					  题目描述
从前有两个无穷数列,其中一个数列a由所有的奇数组成,也就是1,3,5,7,...,另一个数列b由所有的偶数组成,也就是2,4,6,8,...。
现在我们要把这两个数列合并成一个新的数列c,合并方法如下:
1.将a的第1个数字1添加到c的末尾。
2.将b的前2个数字2,4添加到c的末尾。
3.将a的接下来4个数字3,5,7,9添加到c的末尾。
4.将b的接下来8个数字6,8,10,12,14,16,18,20添加到c的末尾。
5.……
现在你需要求这个新的序列c中连续一段的和。
输入
	第一行一个整数T,表示询问数量。
	接下来T行,每一行两个整数l,r,表示求。
输出
	对于每一次询问,输出一行一个整数,表示要求的答案。答案对109+7取模。
样例输入 Copy
3
1 3
5 14
88005553535 99999999999
样例输出	Copy
7
105
761141116
提示
对于30%的数据,保证有T=1,1≤l≤r≤105;
对于50%的数据,保证有1≤l≤r≤105;
对于100%的数据,保证有1≤T≤100000,1≤l≤r≤1018;
在所有的数据中均匀分布着50%的数据,保证l=1。

思路:根据题意模拟即可,用到前缀和数组s[i]表示前i个操作一共维护了多少数,然后把奇数次操作和偶数次操作分别用等差数列求和公式计算出即可。

代码:

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+15;
typedef long long ll;
struct node{
int x,y,step;
};
queue<node>q;
ll mod=1e9+7;
ll p[N],ji[N],ou[N],sji[N],sou[N],s[N];
int main()
{
    p[1]=1; ji[1]=1; ou[2]=2;   s[1]=1;s[2]=3;  sji[1]=1;sji[2]=1;sou[1]=0;sou[2]=2;
    for(int i=2;i<=62;i++)
    {
        p[i]=p[i-1]*2;
        //sp[i]=sp[i-1]+p[i];
    }
    for(int i=3;i<=62;i++)
    {
        if(i%2==1){
            ji[i]=ji[i-2]*4;    s[i]=s[i-1]+ji[i];
        }
        else{
            ou[i]=ou[i-2]*4;    s[i]=s[i-1]+ou[i];
        }
    }
    for(int i=3;i<=62;i++)
    {
        sji[i]=sji[i-1]+ji[i];  sou[i]=sou[i-1]+ou[i];
    }
    ll t,l,r;
    cin>>t;
    while(t--)
    {
        scanf("%lld%lld",&l,&r);  ll ans1=0,ans2=0,l1=0,l2=0;
        l--;
        l1=0;l2=0;
        for(int i=0;i<=62;i++)
        {
            if(s[i+1]>l)
            {
                l1=sji[i];l2=sou[i];
                ll d=l-s[i];
                if( (i+1)%2==1)
                    l1+=d;
                else
                    l2+=d;
              //  cout<<l1<<" "<<l2<<" ";
                if(l2>mod)
                    l2%=mod;
                if(l1>mod)
                    l1%=mod;
                ans1+=l2*(l2+1);
                //ans1%=mod;
                ans1+=l1*(l1+1)-l1;
                ans1%=mod;
                //cout<<ans1<<endl;
                break;
            }
        }
        l1=0;l2=0;
        for(int i=0;i<=62;i++)
        {
            if(s[i+1]>r)
            {
                l1=sji[i];l2=sou[i];
                ll d=r-s[i];
                if( (i+1)%2==1)
                    l1+=d;
                else
                    l2+=d;
                //cout<<l1<<" "<<l2<<" ";
                if(l2>mod)
                    l2%=mod;
                if(l1>mod)
                    l1%=mod;
                ans2+=l2*(l2+1);
                //ans2%=mod;
                ans2+=l1*(l1+1)-l1;
                ans2%=mod;
                //cout<<ans2<<endl;
                break;
            }
        }
        ll op=ans2-ans1;    op%=mod;    op+=mod;    op%=mod;
        printf("%lld\n",op);
        //cout<<ans1<<" "<<ans2<<" "<<(ans2-ans1)<<endl;
    }
    return 0;
}
							B相似
						   题目描述
对于一个排列p,我们定义区间[l,r]的最小值的位置是fp(l,r)。
我们定义两个长度为n的排列a和b相似,当且仅当对于任意的1≤l≤r≤n,有fa(l,r)=fb(l,r)。
现在对于一个确定的a,你需要求出有多少个b满足a和b相似。
输入
第一行一个整数n,表示排列的长度。
接下来一行n个整数,表示给定的排列a。
输出
输出一行一个整数,表示答案。
由于答案可能会很大,你只需要输出答案对998244353取模后的值。
样例输入 Copy
5
3 4 1 2 5
样例输出	Copy
6
提示
样例解释:
共有6种合法的排列:
{2,3,1,4,5},{2,4,1,3,5},{2,5,1,4,3},{3,4,1,2,5},{3,5,1,2,4},{4,5,1,2,3}。

对于20%的数据,保证1≤n≤10;
对于40%的数据,保证1≤n≤20;
对于60%的数据,保证1≤n≤5000;
对于100%的数据,保证1≤n≤500000;
在所有数据中均匀分布着50%的数据,保证数据随机。

·思路:对于样例来说,很容易发现 6=cal(4,2);然后cal(4,2)中的4是a数组中 1 左边数字的数量和右边数字的数量之和,2是a数组中1左边的数量。然后这是因为[1,5]的值肯定是1,然后1的左区间[1,2]和右[3,4]的顺序就是相互独立的,但是区间内数字有序,所以总结一下就是对于一个最小值维护的左右区间,他们相互独立,但区间范围内有序,然后这时候就用模拟单调栈维护每个点的左右区间断点 (这里用了光光的板子) ,用组合数乘起来就行。

代码:

#pragma GCC optimize(2)
#include <bits/stdc++.h>
#include<stdio.h>
#include<queue>
#include<algorithm>
#include<string.h>
#include<iostream>
#define debug(x) cout<<#x<<":"<<x<<endl;
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const ll INF=1e17;
const int maxn=1e6+6;
const int mod=998244353;
const double eps=1e-3;
inline bool read(ll &a)
{char in;bool IsN=false;
    in=getchar();if(in==EOF) return false;while(in!='-'&&(in<'0'||in>'9')) in=getchar();if(in=='-'){ IsN=true;a=0;}else a=in-'0';while(in=getchar(),in>='0'&&in<='9'){a*=10,a+=in-'0';}if(IsN)a=-a;return true;}
ll n,m,p;
ll a[maxn];
int st[maxn];
int pre[maxn],cur[maxn];//表示前方可以控制到多少,后方可以控制到多少。
ll f[maxn];
ll qpow(ll a,ll b)
{
    ll s=1;
    while(b)
    {
        if(b&1)
        {
            s=(s*a)%mod;
        }
        a=(a*a)%mod;
        b/=2;
    }
    return s;
}
ll inv(ll a)
{
    return qpow(a,mod-2);///快速幂,无法判断无解好像
}
ll cal(ll n,ll m)
{
    ll s1=f[n];
    ll s2=f[m]*f[n-m];  s2=s2%mod;
    s1=s1*inv(s2);      s1%=mod;
    return s1;
}
ll get_min()
{
    //维护前方
    int bg=0;
    st[0]=0;//维护最小初始为0下标
    for(int i=1;i<=n;i++)
    {
        while(bg!=0&&a[i]<=a[st[bg]]) //st中存坐标所以栈顶元素这样表示
        //这条件翻译为 只要非空并且栈顶元素比当前元素大也可以相等
            bg--;//栈顶元素就去掉一个
        pre[i]=st[bg];
        st[++bg]=i;//当前元素入栈
    }
    //维护后方
    bg=0;
    st[0]=n+1;//维护最小初始为(n+1)下标
    for(int i=n;i>=1;i--)
    {
        while(bg!=0&&a[i]<=a[st[bg]]) //st中存坐标所以栈顶元素这样表示
        //这条件翻译为 只要非空并且栈顶元素比当前元素大也可以相等
            bg--;//栈顶元素就去掉一个
        cur[i]=st[bg];
        st[++bg]=i;//当前元素入栈
    }
    //输出维护区间
    for(int i=1;i<=n;i++)
    {
        pre[i]++;cur[i]--;
      //  cout<<pre[i]<<" "<<cur[i]<<endl;
    }
    ll maxl=1;
    for(int i=1;i<=n;i++){
        ll nn=cur[i]-pre[i];
        ll mm=cur[i]-i;
        //cout<<cal(nn,mm)<<endl;
        maxl*=cal(nn,mm);
        maxl%=mod;
    }
    return maxl;
}
int main(){
    /*read(n);
    for(int i=1;i<=n;i++) read(a[i]);
    printf("%lld\n",get_min());*/
    f[0]=1;
    for(int i=1;i<=500000;i++)
    {
        f[i]=f[i-1]*i;  f[i]%=mod;
    }
    cin>>n;
    for(int i=1;i<=n;i++)
        cin>>a[i];
    printf("%lld\n",get_min());
    return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_43559193/article/details/106494212