【浮*光】#省选真题# [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; }
【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; //↑↑二分答案 } }
【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); } }
——时间划过风的轨迹,那个少年,还在等你