【浮*光】#省选真题# [JXOI2017] 数列 + 游戏 + 守卫

 

【T1】【p4063】数列

 

  • 有一个长度为n的整数数列ri,构造一个长度为n的整数数列A:
  • 1. 对于任意3≤i≤n,令R为A1至Ai−2中大于等于Ai−1的最小值,
  •     L为A1至Ai−2中小于等于Ai−1的最大值,Ai必须满足L≤Ai≤R。
  •     如果不存在大于等于Ai−1的,那么R=+∞;如果不存在小于等于Ai−1的,那么L=−∞。
  • 2. 1≤Ai≤ri。现在可怜想要知道:共有多少不同的数列A满足这个条件。

 

可以知道L,R在A1至Ai−2中的排名下标是连续的,分别是k,k+1名。

还可以知道,在A1至Ai−2中,没有数在Ai-1~Ai之间。即:填入Ai-1,找它在数列中上下边界确定Ai。

↑↑楼上的思路都有点飘忽,不能按单点的大小得到最终答案,整个序列都必须满足,应考虑整体性。

因为:Ai和Ai-1都在区间[l,r]中,所以对于前方任意Ak,Ai>Ak&&Ai-1>Ak 或 Ai<Ak&&Ai-1<Ak。

即:Ai(R)>Ai-2&&Ai-1>Ai-2 或 Ai(L)<Ai-2&&Ai-1<Ai-2。(我也不知道我到底懂了没...)

设f[i][l][r]表示i位置在[l,r]内取值的方案数。dfs回溯倒序转移:先确定Ai再确定Ai-1。

 

#include <cmath>
#include <iostream>
#include <cstdio>
#include <string>
#include <cstring>
#include <vector>
#include <algorithm>
#include <queue>
#include <stack>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;

#define R register

/*【p4063】数列
有一个长度为n的整数数列ri,构造一个长度为n的整数数列A:
1. 对于任意3≤i≤n,令R为A1至Ai−2中大于等于Ai−1的最小值,
                   L为A1至Ai−2中小于等于Ai−1的最大值,Ai必须满足L≤Ai≤R。
如果不存在大于等于Ai−1的,那么R=+∞;如果不存在小于等于Ai−1的,那么L=−∞。
2. 1≤Ai≤ri。现在可怜想要知道:共有多少不同的数列A满足这个条件。 */

/*【分析】可以知道L,R在A1至Ai−2中的排名下标是连续的,分别是k,k+1名。
还可以知道,在A1至Ai−2中,没有数在Ai-1~Ai之间。即:填入Ai-1,找它在数列中上下边界确定Ai。
↑↑楼上的思路都有点飘忽,不能按单点的大小得到最终答案,整个序列都必须满足,应考虑整体性。
因为:Ai和Ai-1都在区间[l,r]中,所以对于前方任意Ak,Ai>Ak&&Ai-1>Ak 或 Ai<Ak&&Ai-1<Ak。
即:Ai(R)>Ai-2&&Ai-1>Ai-2 或 Ai(L)<Ai-2&&Ai-1<Ai-2。(我也不知道我到底懂了没...)
设f[i][l][r]表示i位置在[l,r]内取值的方案数。dfs回溯倒序转移:先确定Ai再确定Ai-1。*/

#define mod 998244353

void reads(ll &x){ //读入优化(正负整数)
    ll f=1;x=0;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    x*=f; //正负号
}

ll n,a[51],f[51][219][219];

ll dfs(ll x,ll l,ll r){ //f[i][l][r]表示(倒着数)i位置在[l,r]内取值的方案数
    if(x>n) return 1; //到最后位置1,表示这种方案合法的填完了,开始倒序回溯
    ll &res=f[x][l][r]; if(res!=-1) return res; res=0; //记忆化搜索
    for(ll i=max((ll)1,l);i<=min(a[x],r);i++){ //枚举x位置填的数i
        if(i==l||i==r) res=(res+dfs(x+1,i,i))%mod; //只能填一样的
        else res=(res+(dfs(x+1,l,i)+dfs(x+1,i,r))%mod-dfs(x+1,i,i)+mod)%mod;
    } return res; //↑↑查找前一位能填的字符(在区间l,r之内),加和时重复算了一遍
} //即:枚举比后一位大、比后一位小两种情况,求和更新前一位的填法(必须分成两半考虑)

int main(){
    memset(f,-1,sizeof(f)); reads(n); ll maxx=0;
    for(ll i=1;i<=n;i++) reads(a[i]),maxx=max(maxx,a[i]);
    cout<<dfs(1,0,maxx+1)<<endl; return 0; 
}
【p4063】数列 //莫名奇怪的dfs版区间dp?

 

 

【T2】【p4064】加法

 

  • 有一个长度为n的正整数序列A,m个区间[li,ri]和两个正整数a,k。
  • 从这m个区间里选出恰好k个区间,并对每个区间执行一次区间加a的操作。
  • 想要知道:怎么选择区间才能让操作后的序列的[最小值]尽可能的大。

 

#include <cmath>
#include <iostream>
#include <cstdio>
#include <string>
#include <cstring>
#include <vector>
#include <algorithm>
#include <queue>
#include <stack>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;

#define R register

/*【p4064】加法 // 二分答案 + 树状数组维护区间操作
有一个长度为n的正整数序列A,m个区间[li,ri]和两个正整数a,k。
从这m个区间里选出恰好k个区间,并对每个区间执行一次区间加a的操作。
想要知道:怎么选择区间才能让操作后的序列的[最小值]尽可能的大。 */

/*【分析】这题看上去比较温和...(但我是真的傻啊居然没想到二分答案...)*/

#define mod 998244353

void reads(int &x){ //读入优化(正负整数)
    int f=1;x=0;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    x*=f; //正负号
}

const int N=100019; int n,m,k,D,a[N],c[N],cha_fen_[N],d[N];

struct ques{ int l,r; }qi[N]; 

bool cmp(ques a,ques b){ return a.l<b.l; }

inline void add(int x,int val)
 { for(int i=x;i<=n;i+=i&(-i)) c[i]+=val; }

inline int ask(int x)
 { int now=0; for(int i=x;i;i-=i&(-i)) now+=c[i]; return now; }

std::priority_queue< pair<int,int> > q;

bool checks(int x){
    memset(c,0,sizeof(c)); while(!q.empty()) q.pop();
    for(int i=1;i<=n;i++){ d[i]=0; //计算d[i]:当前位置所需的min区间数
        if(a[i]<x) d[i]=ceil(double(x-a[i])/double(D));
        if(d[i]>cha_fen_[i]) return 0; //【剪枝】大于覆盖此点的区间数
    } int now=1,tot=0;
    for(int i=1;i<=n;i++){
        while(qi[now].l==i) q.push(make_pair(qi[now].r,qi[now].l)),now++;
        if(!d[i]) continue; int cnt=ask(i); if(cnt>=d[i]) continue; //树状数组的作用
        while(!q.empty()){ pair<int,int> top_=q.top(); q.pop();
            if(top_.first>=i) add(top_.second,1),add(top_.first+1,-1),cnt++,tot++; //差分
            if(cnt==d[i]) break; //不断取出大顶堆堆顶(包含点i的最大r的区间),在树状数组上修改
        } if(cnt<d[i]) return 0; //此点不能被完全覆盖
    } return (tot<=k); //是否满足区间数<=k
}

int main(){
    int T; cin>>T; while(T--){
        reads(n),reads(m),reads(k),reads(D); int l=2e9,r=2e9,mid,ans=0;
        memset(qi,0,sizeof(qi)); //注意:一定要清零
        for(int i=1;i<=n;i++) cha_fen_[i]=0,reads(a[i]),l=min(a[i],l); r=l+D*k;
        for(int i=1;i<=m;i++) reads(qi[i].l),reads(qi[i].r),
            cha_fen_[qi[i].l]++,cha_fen_[qi[i].r+1]--; sort(qi+1,qi+m+1,cmp);
        for(int i=1;i<=n;i++) cha_fen_[i]+=cha_fen_[i-1]; //差分数组前缀和
        while(l<=r){mid=(l+r)/2;if(checks(mid))ans=mid,l=mid+1;else r=mid-1;}
        cout<<ans<<endl; //↑↑二分答案
   } 
}
【p4064】加法 // 二分答案 + 树状数组维护区间操作

 

 

【T3】【p4065】颜色

 

  • 长度为n的正整数序列Ai(表示颜色),选择一些颜色把这些颜色的所有位置都删去。
  • 想要知道:有多少种删去颜色的方案使得最后剩下来的序列非空且连续。

 

#include <cmath>
#include <iostream>
#include <cstdio>
#include <string>
#include <cstring>
#include <vector>
#include <algorithm>
#include <queue>
#include <stack>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;

#define R register

/*【p4065】颜色 //线段树的经典应用——求点对贡献
长度为n的正整数序列Ai(表示颜色),选择一些颜色把这些颜色的所有位置都删去。
想要知道:有多少种删去颜色的方案使得最后剩下来的序列非空且连续。 */

/*【分析】https://www.cnblogs.com/poorpool/p/8855846.html */

void reads(int &x){ //读入优化(正负整数)
    int f=1;x=0;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    x*=f; //正负号
}

const int inf=0x3f3f3f3f; ll ans=0;

int T,n,col[300005],minn[300005],maxn[300005],sta[300005],din;

struct SGT{

    int sum[1200005]; bool tag[1200005];

    void build(int rt,int l,int r){
        sum[rt]=tag[rt]=0;
        if(l==r) return; int mid=(l+r)>>1;
        if(l<=mid) build(rt<<1,l,mid);
        if(mid<r) build(rt<<1|1,mid+1,r); }
    
    void pushDown(int rt,int l,int r,int lson,int rson,int mid)
       { sum[lson]=mid-l+1; sum[rson]=r-mid;
         tag[lson]=true; tag[rson]=true; tag[rt]=false; }

    void update(int rt,int l,int r,int x,int y){ 
        if(l>=x&&r<=y){ sum[rt]=(r-l+1); tag[rt]=true; return; }
        int mid=(l+r)>>1; 
        if(tag[rt]) pushDown(rt,l,r,rt<<1,rt<<1|1,mid);
        if(x<=mid) update(rt<<1,l,mid,x,y);
        if(mid<y) update(rt<<1|1,mid+1,r,x,y); 
        sum[rt]=sum[rt<<1]+sum[rt<<1|1]; 
    }
    
    int query(int rt, int l, int r, int x, int y){
        if(l>r) return 0;
        if(l>=x&&r<=y) return sum[rt];
        int mid=(l+r)>>1;
        int lson=rt<<1;
        int rson=lson|1;
        int cnt=0;
        if(tag[rt])  pushDown(rt, l, r, lson, rson, mid);
        if(x<=mid)  cnt += query(lson, l, mid, x, y);
        if(mid<y)   cnt += query(rson, mid+1, r, x, y);
        return cnt;
    }

}segment;

int main(){
    cin>>T; while(T--){
        scanf("%d", &n); ans = din = 0;
        for(int i=1; i<=n; i++){
            scanf("%d", &col[i]);
            minn[col[i]] = inf;
            maxn[col[i]] = 0;
        } for(int i=1; i<=n; i++){
            minn[col[i]] = min(minn[col[i]], i);
            maxn[col[i]] = max(maxn[col[i]], i);
        } segment.build(1, 1, n);
        for(int i=1; i<=n; i++){
            if(i==maxn[col[i]])
                segment.update(1, 1, n, minn[col[i]]+1, i);
            sta[++din] = i;
            while(din && maxn[col[sta[din]]]<=i)    din--;
            int pos=!din?1:sta[din]+1;
            ans += (i - pos + 1) - segment.query(1, 1, n, pos, i);
        } printf("%lld\n", ans);
    }
}
【p4065】颜色 //线段树的经典应用——求点对贡献

 

 

 

                                          ——时间划过风的轨迹,那个少年,还在等你

 

posted @ 2019-03-10 20:55  花神&缘浅flora  阅读(273)  评论(0编辑  收藏  举报