【多校训练】2021HDU多校4

【前言】
今天写题手感很好,写的都是1A,然而太蠢了做不动- -
开场自己切了三个水题,队友写了一个水题就开始搞不动了,后面就过了一个05。
字符串如此SB的东西都忘了,今天赶紧补回来了。
rk53,校3/9

另:
在这里插入图片描述

仅次于清北,巅峰了属于是

1001. Calculus

【题意】

给了一堆发散的函数,和非负整数系数,问加起来后整点值 [ 1 , + ∞ ) [1,+\infin) [1,+)求和是否发散。

【思路】

判断系数是否都为0即可。

没看到非负整数,于是还判断了一堆。

【参考代码】

#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=(a),i##ss=(b);i<=i##ss;i++)
#define dwn(i,a,b) for(int i=(a),i##ss=(b);i>=i##ss;i--)
#define deb(x) cerr<<(#x)<<":"<<(x)<<'\n'
#define pb push_back
#define fi first
#define se second
#define hvie '\n'
using namespace std;
typedef pair<int,int> pii;
typedef long long ll;
typedef unsigned long long ull;
typedef double db;
int yh(){
    int ret=0;bool f=0;char c=getchar();
    while(!isdigit(c)){if(c==EOF)return -1;if(c=='-')f=1;c=getchar();}
    while(isdigit(c))ret=(ret<<3)+(ret<<1)+(c^48),c=getchar();
    return f?-ret:ret;
}
const int maxn=3e5+5;
int n;
char s[maxn];
void read_num(int l,int r,bool &G1,bool &is0){
    int a[2]={0,0},cur=0;

    for(int i=l;i<=r;i++){
        // cout<<s[i];
        if(s[i]=='/'){
            cur++;
        }
        else{
            a[cur]=a[cur]*10+(s[i]-'0');
        }
    }
    // cout<<" : !"<<cur<<" "<<a[0]<<" "<<a[1]<<hvie;
    if(cur==1){
        is0=a[0]==0;
        G1=a[0]>=a[1];
    }
    else{
        is0=a[0]==0;
        G1=a[0]>=1;
    }
}
bool proc(int l,int r){
    string t="";
    int i;bool G,is0;
    // for(i=l;i<=r;i++) cout<<s[i];
    // puts    ("");

    for(i=r;i>=l;i--){
        if(!isdigit(s[i])){
            t=s[i]+t;
        }
        else{
            read_num(l,i,G,is0);
            break;
        }
    }
    // cout<<"t="<<t<<" "<<G<<" "<<is0<<hvie;
    // puts("------------");
    if(t==""||t=="/x"||t=="sinx"||t=="cosx"||t=="/cosx"||t=="/sinx"||t=="x"){
        return is0;
    }
    else if(t=="^x"){
        return !G;
    }
    return 0;
}
int main(){
    dwn(_,yh(),1){
        scanf("%s",s+1);
        n=strlen(s+1);
        int lst=0;
        bool bad=0;
        rep(i,1,n){
            if(s[i]=='+'){
                if(!proc(lst+1,i-1)){
                    bad=1;
                    break;
                }
                lst=i;
            }
        }
        if(!bad&&!proc(lst+1,n)) bad=1;
        puts(bad?"NO":"YES");
    }
    return 0;
}
/*
sin
cos
/sin
/cos
x
^x

*/
1002. Kanade Loves Maze Designing

【题意】

一颗 n n n个点,点有颜色的树, w u , v w_{u,v} wu,v表示路径 ( u , v ) (u,v) (u,v)上不同颜色点的个数,求所有的 w w w然后进行一个奇怪的计算(就是乘上个可以预处理的系数)。

n ≤ 2000 n\leq 2000 n2000

【思路】

每个点为根暴力DFS即可。

【参考代码】

#include<bits/stdc++.h>
#define pb push_back
#define mkp make_pair
#define fi first
#define se second
#define ri register int
using namespace std;

typedef double db;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<int,ll> pil;
const int N=2005,mod1=1e9+7,mod2=1e9+9;

int n,a[N][N],pw1[N],pw2[N];
int cnt[N],c[N];
vector<int>G[N];

int add(int x,int y,int mod){return x+y>=mod?x+y-mod:x+y;}
int mul(int x,int y,int mod){return 1ll*x*y%mod;}
int qpow(int x,int y,int mod)
{
    int ret=1;
    for(;y;y>>=1,x=mul(x,x,mod)) 
        if(y&1) ret=mul(ret,x,mod);
    return ret;
}

int now,rt;
void dfs(int x,int fa)
{
    if(!cnt[c[x]]) ++now;
    ++cnt[c[x]];
    a[rt][x]=now;
    for(auto v:G[x])
    {
        if(v==fa) continue;
        dfs(v,x);
    }
    --cnt[c[x]];
    if(!cnt[c[x]]) --now;
}

int main()
{
    //freopen("ttt.in","r",stdin);
    //freopen("a.out","w",stdout);
    for(int i=0;i<N;++i) pw1[i]=qpow(19560929,i,mod1);
    for(int i=0;i<N;++i) pw2[i]=qpow(19560929,i,mod2);    
    int T;scanf("%d",&T);
    while(T--)
    {
        scanf("%d",&n);
        for(int i=1;i<=n;++i) G[i].clear();
        for(int i=2,x;i<=n;++i)
        {
            scanf("%d",&x);
            G[x].pb(i);G[i].pb(x);
        } 
        for(int i=1;i<=n;++i) scanf("%d",&c[i]);
        for(int i=1;i<=n;++i) rt=i,now=0,dfs(i,0);

        
        for(int i=1;i<=n;++i) 
        {
            int ans1=0,ans2=0;
            for(int j=1;j<=n;++j)
            {
                ans1=add(ans1,mul(a[i][j],pw1[j-1],mod1),mod1);
                ans2=add(ans2,mul(a[i][j],pw2[j-1],mod2),mod2);
            }
            printf("%d %d\n",ans1,ans2);
        }
    }
    return 0;
}
 
1003. Cycle Binary

【题意】

一个01串可以表示为 k p + p ′ kp+p' kp+p的形式,其中 p p p是最短的循环节长度, p ′ p' p是循环节的前缀, k k k是重复次数。

求长度为 n n n的所有01串的 k k k的和。

【思路】
显然我不会数学。
在这里插入图片描述
在这里插入图片描述

【参考代码】

n ≤ 1 0 9 n\leq 10^9 n109

#include<bits/stdc++.h>
#include<ext/pb_ds/assoc_container.hpp>
#include<ext/pb_ds/hash_policy.hpp>
using namespace __gnu_pbds;
#define rep(i,a,b) for(int i=(a),i##ss=(b);i<=i##ss;i++)
#define dwn(i,a,b) for(int i=(a),i##ss=(b);i>=i##ss;i--)
#define deb(x) cerr<<(#x)<<":"<<(x)<<'\n'
#define pb push_back
#define fi first
#define se second
#define hvie '\n'
using namespace std;
typedef pair<int,int> pii;
typedef long long ll;
typedef unsigned long long ull;
typedef double db;
ll yh(){
    int ret=0;bool f=0;char c=getchar();
    while(!isdigit(c)){if(c==EOF)return -1;if(c=='-')f=1;c=getchar();}
    while(isdigit(c))ret=(ret<<3)+(ret<<1)+(c^48),c=getchar();
    return f?-ret:ret;
}
const int maxn=5e7+5,N=5e6,mod=998244353;
ll f[maxn],pw[maxn];
ll add(ll x,ll y){x+=y;return x>=mod?x-mod:x;}
ll sub(ll x,ll y){x-=y;return x< 0?x+mod:x;}
ll ksm(ll a,ll b,ll c=1){
    for(;b;b>>=1,a=a*a%mod) if(b&1) c=c*a%mod;
    return c;
}
gp_hash_table<ll,ll>h;
ll S(ll n){
    if(n<=N) return f[n];
    if(h[n]) return h[n];
    ll tmp=2;
    for(ll l=2,r;l<=n;l=r+1){
        r=n/(n/l);
        tmp=add(tmp,(r-l+1)%mod*S(n/l)%mod);
    }
    return h[n]=sub(ksm(2ll,n+1),tmp);
}
ll g[maxn];
int main(){
    // f[1]=1;
    pw[0]=1;
    f[0]=0;
    rep(i,1,N){
        pw[i]=pw[i-1]*2ll%mod;
        f[i]=add(f[i],pw[i]);
        for(int j=i+i;j<=N;j+=i) f[j]=sub(f[j],f[i]);
    }
    rep(i,1,N) g[i]=f[i],f[i]=add(f[i-1],f[i]);
    dwn(_,yh(),1){
        ll n=yh();
        ll ans=0;
        ll m=n/2;
        for(ll i=1;i<=m;i++){
            ans=add(ans,(n/i-1)*g[i]%mod);
        }
        cout<<add(ksm(2ll,n),ans)<<hvie;
    }
    return 0;
}
1004. Display Substring

【题意】

一个长度为 n n n的字符串 S S S,每个字符有一个权值 w i w_i wi,求所有本质不同的子串中,权值第 K K K小的权值是多少。

n ≤ 1 0 5 , w i ≤ 100 n\leq 10^5,w_i\leq 100 n105,wi100

【思路】

首先一般求第 K K K啥的都可以二分这个权值 c c c,问题转化为求有多少个本质不同的子串权值小于等于 c c c。然后给权值求一个前缀和可以很方便求出一段子串的权值。

考虑后缀自动机,自动机上每个点实际上已经代表了本质不同的子串,和它对应可能的长度区间,我们每次二分这个长度就可以求出有多少个可行的长度了。

复杂度是 O ( n log ⁡ n log ⁡ W ) O(n\log n\log W) O(nlognlogW)

一个可以跑的更快的优化是,实际上每个节点对应的endpos是往上传递的,而我们二分长度时也是从某个endpos处二分可行的长度,那么我们只需要在parent树的叶子处二分就可以了,可能随机数据下能快不少,然后由于内外层二分移动方向是相同的,所以再记一个之前二分区间甚至可以更快。

事实上,据说由于这个特性可以把长度二分的这个 log ⁡ n \log n logn优化掉,但是我不会。

【参考代码】

#include<bits/stdc++.h>
#define pb push_back
#define mkp make_pair
#define fi first
#define se second
#define ri register int
using namespace std;

typedef double db;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<int,ll> pil;
const int N=2e5+10,inf=0x3f3f3f3f;

int n;
ll K;
int pos[N],sum[N],val[26];
char s[N];

struct SAM
{
    ll tot;
    int goal;
    int sz,las,rt;
    int fa[N],mx[N],ch[N][26];
    void init()
    {
        for(int i=0;i<=sz;++i) 
        {
            fa[i]=mx[i]=pos[i]=0;
            memset(ch[i],0,sizeof(ch[i]));
        }
        sz=las=rt=1;
    }
    void extend(int x,int i)
    {
        int p,q,np,nq;
        p=las;las=np=++sz;mx[np]=mx[p]+1;pos[np]=i;
        for(;p && !ch[p][x];p=fa[p]) ch[p][x]=np;
        if(!p) fa[np]=1;
        else
        {
            q=ch[p][x];
            if(mx[q]==mx[p]+1) fa[np]=q;
            else
            {
                nq=++sz;mx[nq]=mx[p]+1;pos[nq]=i;
                memcpy(ch[nq],ch[q],sizeof(ch[q]));
                fa[nq]=fa[q];fa[q]=fa[np]=nq;
                for(;ch[p][x]==q;p=fa[p]) ch[p][x]=nq;
            }
        }
    }
    ll check(int k)
    {
        goal=k;tot=0;
        for(int x=2;x<=sz;++x)
        {
            if(!pos[x]) continue;
            int l=mx[fa[x]]+1,r=mx[x],res=mx[fa[x]];
            //printf("x:%d pos[x]:%d goal:%d [%d,%d]\n",x,pos[x],goal,l,r);
            while(l<=r)
            {
                int mid=(l+r)>>1;
                if(sum[pos[x]]-sum[pos[x]-mid]<=goal) res=mid,l=mid+1;
                else r=mid-1;
            }
            //printf("res:%d add:%d\n",res,res-(mx[fa[x]]+1)+1);
            tot+=res-(mx[fa[x]]+1)+1;
        }
        //printf("%d!!!tot:%lld\n",k,tot);
        return tot;
    }
}S;

int main()
{ 
    //freopen("1004.in","r",stdin);
    //freopen("my.out","w",stdout);
    int T;scanf("%d",&T);
    while(T--)
    {
        scanf("%d%lld%s",&n,&K,s+1);
        for(int i=0;i<26;++i) scanf("%d",&val[i]);
        for(int i=1;i<=n;++i)
            sum[i]=sum[i-1]+val[s[i]-'a'];
        
        S.init();
        for(int i=1;i<=n;++i) 
        {
            S.extend(s[i]-'a',i);
        }
        int l=1,r=sum[n],ans=-1;
        while(l<=r)
        {
            int mid=(l+r)>>1;
            if(S.check(mid)>=K) ans=mid,r=mid-1;
            else l=mid+1;
        }
        printf("%d\n",ans);
    }
    return 0;
}
 
1005. Didn’t I Say to Make My Abilities Average in the Next Life?!

【题意】

长度为 n n n的序列,有 m m m个询问,每次询问一个区间 [ l , r ] [l,r] [l,r]所有子区间最大值与最小值的平均值的期望。

n , m ≤ 2 × 1 0 5 n,m\leq 2\times 10^5 n,m2×105

【思路】

实际上我们要做的就是求每个区间的所有子区间的最大值和最小值的和。

这个东西是个原题HNOI2016的序列,这里就不说了。

大概可以用莫队、笛卡尔树、历史最值线段树等多种方法做。

【参考代码】

莫队 O ( n n log ⁡ n ) O(n\sqrt n\log n) O(nn logn)

#include <bits/stdc++.h>
using namespace std;
#define int long long

const int maxn = 210010;
const int MOD = 1e9 + 7;

int t, n, Q;
int belong[maxn];
struct Node {
    int l, r, id;
    bool friend operator < (const Node &a, const Node &b) {
        if (belong[a.l] == belong[b.l]) return a.r < b.r;
        return belong[a.l] < belong[b.l];
    }
} q[maxn];
int a[maxn];

int ksm(int a, int b, int p) {
    int s = 1;
    for (; b; b >>= 1, a = a * a % p) if (b & 1) s = s * a % p;
    return s;
}

namespace Mn {
int loger[maxn], minn[maxn][30];
int minid[maxn][30];
int a[maxn];
inline void pre(int n) {
    loger[1] = 0;
    for (int i = 2; i <= n; ++i) {
        loger[i] = loger[i - 1];
        if ( i == (1 << loger[i] + 1)) ++ loger[i];
    }
    for (int i = n; i >= 1; --i) {
        minn[i][0] = a[i];
        minid[i][0] = i;
        for (int j = 1; i + (1 << j) - 1 <= n; ++j) {
            minn[i][j] = min(minn[i][j - 1], minn[i + (1 << j - 1)][j - 1]);
            if (minn[i][j] == minn[i][j - 1]) minid[i][j] = minid[i][j - 1];
            if (minn[i][j] == minn[i + (1 << j - 1)][j - 1]) minid[i][j] = minid[i + (1 << j - 1)][j - 1];
        }
    }
}
inline int queryMin(int l, int r) {
    int k = loger[r - l + 1];
    if (minn[l][k] < minn[r - (1 << k) + 1][k]) return minid[l][k];
    return minid[r - (1 << k) + 1][k];
}
int ansmn[maxn], flMn[maxn], frMn[maxn];
int sta[maxn], top;
inline void dpMn(int n, int *f) {
    sta[top = 1] = 0;
    for (int i = 1; i <= n; ++i) {
        while (a[sta[top]] > a[i]) -- top;
        f[i] = (i - sta[top]) * a[i] + f[sta[top]];
        sta[++top] = i;
    }
}
inline int upd_R(int l, int r) {
    int p = queryMin(l, r + 1);
    return ((p - l + 1) * a[p] % MOD + flMn[r + 1] - flMn[p]) % MOD;
}
inline int upd_L(int l, int r) {
    int p = queryMin(l - 1, r);
    return ((r - p + 1) * a[p] % MOD + frMn[l - 1] - frMn[p]) % MOD;
}
void Mn() {
    a[0] = -(1LL << 60);
    pre(n);
    dpMn(n, flMn);
    reverse(a + 1, a + n + 1);
    dpMn(n, frMn);
    reverse(a + 1, a + n + 1);
    reverse(frMn + 1, frMn + n + 1);
    a[0] = 0;
    int L = 1, R = 1, ans = a[1];
    for (int i = 1; i <= Q; ++i) {
        while (R < q[i].r) ans = (ans + upd_R(L, R++)) % MOD;
        while (R > q[i].r) ans = (ans - upd_R(L, --R)) % MOD;
        while (L > q[i].l) ans = (ans + upd_L(L--, R)) % MOD;
        while (L < q[i].l) ans = (ans - upd_L(++L, R)) % MOD;
        ans = (ans + MOD) % MOD;
        int l = (q[i].r - q[i].l + 1);
        ansmn[q[i].id] = ans * ksm(l * (l + 1) % MOD, MOD - 2, MOD) % MOD;
    }
}
}

namespace Mx {
int loger[maxn], maxx[maxn][30], a[maxn];
int maxid[maxn][30];
inline void pre(int n) {
    loger[1] = 0;
    for (int i = 2; i <= n; ++i) {
        loger[i] = loger[i - 1];
        if ( i == (1 << loger[i] + 1)) ++ loger[i];
    }
    for (int i = n; i >= 1; --i) {
        maxx[i][0] = a[i];
        maxid[i][0] = i;
        for (int j = 1; i + (1 << j) - 1 <= n; ++j) {
            maxx[i][j] = max(maxx[i][j - 1], maxx[i + (1 << j - 1)][j - 1]);
            if (maxx[i][j] == maxx[i][j - 1]) maxid[i][j] = maxid[i][j - 1];
            if (maxx[i][j] == maxx[i + (1 << j - 1)][j - 1]) maxid[i][j] = maxid[i + (1 << j - 1)][j - 1];
        }
    }
}
inline int queryMax(int l, int r) {
    int k = loger[r - l + 1];
    if (maxx[l][k] > maxx[r - (1 << k) + 1][k]) return maxid[l][k];
    return maxid[r - (1 << k) + 1][k];
}
int ansmx[maxn], flMx[maxn], frMx[maxn];
int sta[maxn], top;
inline void dpMx(int n, int *f) {
    sta[top = 1] = 0;
    for (int i = 1; i <= n; ++i) {
        while (a[sta[top]] < a[i]) -- top;
        f[i] = (i - sta[top]) * a[i] + f[sta[top]];
        sta[++top] = i;
    }
}
inline int upd_R(int l, int r) {
    int p = queryMax(l, r + 1);
    return ((p - l + 1) * a[p] % MOD + flMx[r + 1] - flMx[p]) % MOD;
}
inline int upd_L(int l, int r) {
    int p = queryMax(l - 1, r);
    return ((r - p + 1) * a[p] % MOD + frMx[l - 1] - frMx[p]) % MOD;
}
void Mx() {
    a[0] = 1LL << 60;
    pre(n);
    dpMx(n, flMx);
    reverse(a + 1, a + n + 1);
    dpMx(n, frMx);
    reverse(a + 1, a + n + 1);
    reverse(frMx + 1, frMx + n + 1);
    a[0] = 0;
    int L = 1, R = 1, ans = a[1];
    for (int i = 1; i <= Q; ++i) {
        while (R < q[i].r) ans = (ans + upd_R(L, R++)) % MOD;
        while (R > q[i].r) ans = (ans - upd_R(L, --R)) % MOD;
        while (L > q[i].l) ans = (ans + upd_L(L--, R)) % MOD;
        while (L < q[i].l) ans = (ans - upd_L(++L, R)) % MOD;
        ans = (ans + MOD) % MOD;
        int l = (q[i].r - q[i].l + 1);
        ansmx[q[i].id] = ans * ksm(l * (l + 1) % MOD, MOD - 2, MOD) % MOD;
    }
}
}

void input() {
    cin >> n >> Q;
    for (int i = 1; i <= n; ++i) {
        cin >> a[i];
        Mx::a[i] = Mn::a[i] = a[i];
    }
    int block = n / sqrt(Q) + 1;
    for (int i = 1; i <= n; ++i) belong[i] = (i / block) + 1;
    for (int i = 1; i <= Q; ++i) {
        cin >> q[i].l >> q[i].r;
        q[i].id = i;
    }
    sort(q + 1, q + Q + 1);
}

signed main() {
    ios_base::sync_with_stdio(false);
    cin.tie(NULL);
    int Case;
    cin >> Case;
    while (Case--) {
        input();
        Mn::Mn();
        Mx::Mx();
        for (int i = 1; i <= Q; ++i) {
            cout << (Mx::ansmx[i] + Mn::ansmn[i]) % MOD << endl;
        }
    }
    return 0;
}
1006. Directed Minimum Spanning Tree

【题意】

在这里插入图片描述

【思路】
显然我不会图论。
在这里插入图片描述

1007. Increasing Subsequence

【题意】

给定一个长度为 n n n的排列 a i a_i ai,问有多少种极长上升子序列。(即不是任何一个其他上升子序列的子序列)

n ≤ 1 0 5 n\leq 10^5 n105

【思路】

首先我们考虑怎么暴力,设 f i f_i fi表示所有考虑到第 i i i位的上升子序列的个数,那么答案就是每个后面不存在比它大的数字的 i i i f i f_i fi之和。而初值是所有左边不存在比它小的数字的 f f f为1。

转移呢?我们考虑一个子序列 x < y < z x<y<z x<y<z,如果 a x < a y < a z a_x<a_y<a_z ax<ay<az,那么 f x f_x fx必然不可能转移到 f z f_z fz,因为中间有个 a y a_y ay,于是能转移的就很明显了:对于一个 i i i,我们将比 a i a_i ai小的 i i i前的数全部拿出来做单调栈(栈顶最小),所有剩下的数就是能转移到 f i f_i fi的。

于是问题转化为了维护一个单调栈和单调栈内 f f f的和。

一种方法是考虑维护这样一个函数: f i n d ( l , r , m x ) find(l,r,mx) find(l,r,mx),表示在 [ l , r ] [l,r] [l,r],只用 ≥ m x \geq mx mx的数字组成的单调栈的 f f f的和是多少,我们按照数字顺序从小到大考虑,这样每个位置的答案就是 f i n d ( 1 , i , 0 ) find(1,i,0) find(1,i,0)

这个东西是可以用线段树维护的,具体来说,线段树的每个位置维护它的右孩子的最大值 r m x rmx rmx,左区间 f i n d ( l , m i d , r m x ) find(l,mid,rmx) find(l,mid,rmx)的答案 l a n s lans lans。这是维护线段树上每个节点的时候,那么怎么维护一个区间的这个函数呢?

我们考虑从右到左合并区间,现在右边已经合出来一个区间 B B B(由线段树上某些节点合成),左边的区间是 A A A(线段树上的一个节点)。设 B B B中最大值为 P m x Pmx Pmx,那么接下来有几种情况:

  • m x [ A . r s o n ] > P m x mx[A.rson]>Pmx mx[A.rson]>Pmx,这说明右孩子对整个答案有贡献,而我们已经算出了 l a n s lans lans,那么就可以只递归右孩子。
  • m x [ A . r s o n ] < P m x mx[A.rson]<Pmx mx[A.rson]<Pmx,这说明右孩子对答案没贡献,我们递归左孩子就行。

这里这个 l a n s lans lans的维护就是为了让线段树只递归一边,保证复杂度。

l a n s lans lans的维护在 u p d a t e update update的时候调用 f i n d find find来维护即可。

复杂度 O ( n log ⁡ 2 n ) O(n\log ^2n) O(nlog2n)

还有一种方法是用分治来解决这个问题,比赛时我们写的也是这个,不过处理贡献没想明白,后面才想清楚。

当处理区间 [ l , r [l,r [l,r 时,令 m m m [ l , r ] [l,r] [l,r] 的中点,对 a [ l , r ] a[l,r] a[l,r]中所有元素按值依次从小到大处理。对 [ l , m ] [l,m] [l,m] [ m + 1 , r ] [m+1,r] [m+1,r]分别建单调栈。左边单调栈中的元素值从小到大,位置从大到小。右边的单调栈中元素值从小到大,位置从小到大。处理左边的元素时直接往单调栈里丢,处理右边的元素 a [ i ] a[i] a[i]时通过单调栈找到 [ m + 1 , i − 1 ] [m+1,i-1] [m+1,i1] 中比他小的最靠右侧的元素 a [ j ] a[j] a[j] 。然后在左侧的单调栈中找到比 a [ j ] a[j] a[j]大的第一个元素和比 a [ i ] a[i] a[i]小的最后一个元素。区间 [ l , m ] [l,m] [l,m] f [ i ] f[i] f[i]的贡献就来自单调栈里这两个元素之间的元素的 f f f 值之和。

复杂度同样是 O ( n log ⁡ 2 n ) O(n\log^2n) O(nlog2n)的,不过常数会小一点。

具体实现见代码。

【参考代码】

线段树

#include<bits/stdc++.h>
#define pb push_back
#define mkp make_pair
#define fi first
#define se second
#define ri register int
using namespace std;

typedef double db;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<int,ll> pil;
const int N=1e5+10,inf=0x3f3f3f3f,mod=998244353;

int n;
int a[N],b[N],f[N];

void up(int &x,int y){x+=y;x%=mod;}

struct Seg
{
    #define ls (x<<1)
    #define rs (x<<1|1)
    
    int Pmx;
    struct node
    {
        int sum,mx,lans;
    }t[N<<2];
    void build(int x,int l,int r)
    {
        t[x].sum=0;t[x].mx=-1;t[x].lans=0;
        if(l==r) return;
        int mid=(l+r)>>1;
        build(ls,l,mid);build(rs,mid+1,r);
    }
    int query(int x,int l,int r,int L,int R)//find [L,R] use>P sum f
    {
        if(t[x].mx<Pmx) return 0;
        if(l==r)
        {
            if(t[x].mx<Pmx) return 0;
            Pmx=max(Pmx,t[x].mx);
            return t[x].sum;
        }
        int mid=(l+r)>>1,ret=0;
        if(L<=l && r<=R)
        {
            if(Pmx>t[rs].mx) return query(ls,l,mid,L,R);
            up(ret,query(rs,mid+1,r,L,R));Pmx=max(Pmx,t[x].mx);
            up(ret,t[x].lans);
        }
        else 
        {
            if(R>mid) up(ret,query(rs,mid+1,r,L,R));
            if(L<=mid) up(ret,query(ls,l,mid,L,R));
        }
        return ret;
    }
    void update(int x,int l,int r,int p,int val)
    {
        if(l==r) 
        {
            t[x].mx=a[l];t[x].sum=val;
            return;
        }
        int mid=(l+r)>>1;
        if(p<=mid) update(ls,l,mid,p,val);
        else update(rs,mid+1,r,p,val);

        Pmx=t[rs].mx;
        t[x].lans=query(ls,l,mid,l,mid);
        t[x].sum=t[ls].sum+t[rs].sum;
        t[x].mx=max(t[ls].mx,t[rs].mx);
    }
    #undef ls
    #undef rs
}tr;

int main()
{ 
    int T;scanf("%d",&T);
    while(T--)
    {
        scanf("%d",&n);
        for(int i=1,tmi=inf;i<=n;++i) 
        {
            scanf("%d",&a[i]);
            b[a[i]]=i;
            if(tmi>a[i]) f[i]=1,tmi=a[i];
            else f[i]=0;
        }
        tr.build(1,1,n);
        for(int i=1;i<=n;++i)
        {
            if(!f[b[i]])
            {
                tr.Pmx=0;
                f[b[i]]=tr.query(1,1,n,1,b[i]-1);
            }
            tr.update(1,1,n,b[i],f[b[i]]);
        }
        int ans=0;
        for(int i=n,tmx=0;i;--i)
        {
            if(tmx<a[i]) up(ans,f[i]),tmx=a[i];
        }
        printf("%d\n",ans);
    }
    return 0;
}
 

分治

#include<bits/stdc++.h>
#define pb push_back
#define mkp make_pair
#define fi first
#define se second
#define ri register int
using namespace std;

typedef double db;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<int,ll> pil;
const int N=1e5+10,inf=0x3f3f3f3f,mod=998244353;

int n;
int a[N],f[N],sum[N];
int stl[N],str[N];
pii b[N];

void up(int &x,int y){x+=y;x%=mod;}
int upm(int x){x%=mod;return x>=0?x:x+mod;}

int find(int num,int l,int r)//find b[stl[pos]].fi>num
{
    int ret=r+1;
    while(l<=r)
    {
        int mid=(l+r)>>1;
        if(a[stl[mid]]>=num) ret=mid,r=mid-1;
        else l=mid+1;
    }
    return ret;
}

void divide(int l,int r)
{
    if(l==r) return;
    int mid=(l+r)>>1;
    divide(l,mid);

    //printf("now:%d %d\n",l,r);

    int lenl=0,lenr=0,cnt=0;
    for(int i=l;i<=r;++i) b[++cnt]=mkp(a[i],i);
    sort(b+1,b+cnt+1);
    for(int i=1;i<=cnt;++i)
    {
        if(b[i].se<=mid)
        {
            while(lenl && b[i].se>stl[lenl]) --lenl;
            stl[++lenl]=b[i].se;
            sum[lenl]=upm(sum[lenl-1]+f[b[i].se]);
            //printf("push:%d\n",b[i].fi);
        }
        else
        {
            while(lenr && b[i].se<str[lenr]) --lenr;
            str[++lenr]=b[i].se;

            //printf("nowstack:\n");
            //for(int i=1;i<=lenl;++i) printf("%d ",stl[i]);
            //puts("");
            int t=a[str[lenr-1]]+1;
            //printf("find:%d %d\n",t,b[i].fi);
            
            int lp=find(t,1,lenl),rp=find(b[i].fi,1,lenl)-1;
            //printf("lp:%d rp:%d\n",lp,rp);
            if(lp>rp) continue;
            up(f[b[i].se],upm(sum[rp]-sum[lp-1]));
        }
    }
    //for(int i=1;i<=n;++i) printf("%d ",f[i]);
    //puts("");
    divide(mid+1,r);
}

int main()
{ 
    int T;scanf("%d",&T);
    while(T--)
    {
        scanf("%d",&n);
        for(int i=1,tmi=inf;i<=n;++i) 
        {
            scanf("%d",&a[i]);
            if(tmi>a[i]) f[i]=1,tmi=a[i];
            else f[i]=0;
        }

        divide(1,n);

        int ans=0;
        for(int i=n,tmx=0;i;--i)
        {
            if(tmx<a[i]) up(ans,f[i]),tmx=a[i];
        }
        printf("%d\n",ans);
    }
    return 0;
}
 
1008. Lawn of the Dead

【题意】

一个 n × m n\times m n×m的网格图,从左上角出发走,每次只能往下或往右走,有其中 k k k个格子不能走。问一共有多少格子可达。

n , m , k ≤ 1 0 5 n,m,k\leq 10^5 n,m,k105

【思路】

我们每行能走到哪些格子可以由上一行转移过来。

我们可以考虑维护当前行有哪些格子可以走到。

按不能走的格子为分隔点,实际上就是要找到上一行在这个区间内能到达的最左的格子。

线段树维护即可,操作就是区间置0,区间置1和查询区间最左的1

复杂度 O ( k log ⁡ m ) O(k\log m) O(klogm)

【参考代码】

#include<bits/stdc++.h>
#define pb push_back
#define mkp make_pair
#define fi first
#define se second
#define ri register int
using namespace std;

typedef double db;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<int,ll> pil;
const int N=2e5+10,inf=0x3f3f3f3f;

int n,m,k;
vector<int>G[N];

struct Seg
{
    #define ls (x<<1)
    #define rs (x<<1|1)
    int lmost[N<<2],tag[N<<2];
    void pushup(int x)
    {
        lmost[x]=min(lmost[ls],lmost[rs]);
    }
    void pushdown(int x,int l,int r)
    {
        if(tag[x]!=-1)
        {
            if(tag[x])
            {
                lmost[ls]=l;lmost[rs]=((l+r)>>1)+1;
            }
            else lmost[ls]=lmost[rs]=inf;
            tag[ls]=tag[x];tag[rs]=tag[x];tag[x]=-1;
        }
    }
    void build(int x,int l,int r)
    {
        tag[x]=-1;lmost[x]=inf;
        if(l==r) 
        {
            lmost[x]=(l==1?1:inf);
            return;
        }
        int mid=(l+r)>>1;
        build(ls,l,mid);build(rs,mid+1,r);
        pushup(x);
    }
    void update(int x,int l,int r,int L,int R,int op)
    {
        if(L>R) return;
        if(L<=l && r<=R)
        {
            tag[x]=op;
            if(op) lmost[x]=l;
            else lmost[x]=inf;
            return;
        }
        pushdown(x,l,r);
        int mid=(l+r)>>1;
        if(L<=mid) update(ls,l,mid,L,R,op);
        if(R>mid) update(rs,mid+1,r,L,R,op);
        pushup(x);
    }
    int find(int x,int l,int r,int L,int R)
    {
        if(L>R) return inf;
        if(L<=l && r<=R) return lmost[x];
        int ret=inf,mid=(l+r)>>1;
        pushdown(x,l,r);
        if(L<=mid) ret=min(ret,find(ls,l,mid,L,R));
        if(R>mid) ret=min(ret,find(rs,mid+1,r,L,R));
        pushup(x);
        return ret;
    }
    #undef ls
    #undef rs
}tr;

int main()
{ 
    int T;scanf("%d",&T);
    while(T--)
    {
        scanf("%d%d%d",&n,&m,&k);
        for(int i=1,x,y;i<=k;++i)
        {
            scanf("%d%d",&x,&y);
            G[x].pb(y);
        }
        tr.build(1,1,m);

        ll ans=0;
        for(int i=1;i<=n;++i)
        {
            G[i].pb(m+1);
            sort(G[i].begin(),G[i].end());
            int sz=G[i].size(),las=0;
            for(int j=0;j<sz;++j)
            {
                int t=tr.find(1,1,m,las+1,G[i][j]-1);
                //printf("row:%d find:[%d,%d] %d\n",i,las+1,G[i][j]-1,t);
                if(t!=inf)
                {
                    ans+=G[i][j]-t;
                    tr.update(1,1,m,G[i][j],G[i][j],0);
                    tr.update(1,1,m,las+1,t-1,0);
                    tr.update(1,1,m,t,G[i][j]-1,1);
                }
                else tr.update(1,1,m,las+1,G[i][j],0);
                las=G[i][j];
            }
            G[i].clear();
        }
        printf("%lld\n",ans);
    }
    return 0;
}
 
1009. License Plate Recognition

【题意】

给一个车牌,求每个文字的左右边界。

【思路】

可以垂直投影到一维区间,后六个为数字和字母,它们的区间是连续的,剩下的就是第一个汉字的了。

比赛时比较蠢写了个BFS。

【参考代码】

#include<bits/stdc++.h>
#define pb push_back
#define mkp make_pair
#define fi first
#define se second
#define ri register int
using namespace std;

typedef double db;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<int,ll> pil;
const int N=2005,mod1=1e9+7,mod2=1e9+9;

int n,m,cnt;
char a[35][105];
pii res[N];
queue<pii>q;
int dx[]={0,1,0,-1},dy[]={1,0,-1,0};

int inmp(int x,int y)
{
    return 1<=x && x<=n && 1<=y && y<=m && a[x][y]=='#';
}

pii bfs(int x,int y)
{
    while(!q.empty()) q.pop();
    q.push(mkp(x,y));a[x][y]='.';
    int mi=y,mx=y;
    while(!q.empty())
    {
        x=q.front().fi,y=q.front().se;q.pop();
        for(int i=0;i<4;++i)
        {
            int nx=x+dx[i],ny=y+dy[i];
            if(!inmp(nx,ny)) continue;
            mi=min(ny,mi);mx=max(ny,mx);
            q.push(mkp(nx,ny));a[nx][ny]='.';
        }
    }
    return mkp(mi,mx);
}

int main()
{ 
    int T;scanf("%d",&T);
    n=30;m=100; 
    for(int tt=1;tt<=T;++tt)
    {
        cnt=0;
        for(int i=1;i<=n;++i) scanf("%s",a[i]+1);
        //for(int i=1;i<=n;++i) printf("%s\n",a[i]+1);
        for(int i=1;i<=n;++i)
        {
            for(int j=1;j<=m;++j)
                if(a[i][j]=='#') res[++cnt]=bfs(i,j);
        }
        sort(res+1,res+cnt+1);
       // for(int i=1;i<=cnt;++i) printf("get:%d %d\n",res[i].fi,res[i].se);
        printf("Case #%d:\n",tt);
        int mi=m,mx=0;
        for(int i=1;i<=cnt-6;++i) mi=min(mi,res[i].fi),mx=max(mx,res[i].se);
        printf("%d %d\n",mi,mx);
        for(int i=cnt-5;i<=cnt;++i) printf("%d %d\n",res[i].fi,res[i].se);

    }
    return 0;
}
 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值