NOI.AC: NOIP2018 全国模拟赛习题练习

闲谈:

  最后一个星期还是不浪了,做一下模拟赛(还是有点小虚)


#30.candy

题目:

  有一个人想买糖吃,有两家商店A,B,A商店中第i个糖果的愉悦度为Ai,B商店中第i个糖果的愉悦度为Bi

  给出n,W,表示每个商店都有n个糖果且两个商店的每个糖果的价格都是W

  求出最大的min(Sa,Sb)-D*W

  其中Sa表示在A商店买的糖果的愉悦度之和,Sb表示在B商店中买的糖果的愉悦度之和,D表示总共在两家商店买的糖果数

题解:

  直接乱搞,贪心想一想,每次取糖果肯定先从愉悦度大的取为优

  那么我们只要在取糖果的时候,一旦有一个商店的愉悦度和比较大,则买另一个商店的糖果,因为这样就能增大min(Sa,Sb)

  然后在过程中判断一下其他情况就行了

参考代码:

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<algorithm>
using namespace std;
typedef long long LL;
int a[110000],b[110000];
int main()
{
    int n;LL W;
    scanf("%d%lld",&n,&W);
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    reverse(a+1,a+n+1);
    for(int i=1;i<=n;i++) scanf("%d",&b[i]);
    reverse(b+1,b+n+1);
    LL ans=0,d1=0,d2=0,t1=0,t2=0;
    for(int i=1;i<=2*n;i++)
    {
        if(a[t1+1]<=W&&b[t2+1]<=W) break;
        if(d1==d2)
        {
            if(t1+1<=n&&a[t1+1]>b[t2+1]) d1+=a[++t1];
            else if(t2+1<=n) d2+=b[++t2];
        }
        else if(d1>d2)
        {
            if(t2+1<=n) d2+=b[++t2];
            else
            {
                ans=max(ans,min(d1,d2)-W*(i-1));
                break;
            }
        }
        else
        {
            if(t1+1<=n) d1+=a[++t1];
            else
            {
                ans=max(ans,min(d1,d2)-W*(i-1));
                break;
            }
        }
        ans=max(ans,min(d1,d2)-W*i);
    }
    printf("%lld\n",ans);
    return 0;
}
#30

#31.MST

题目:

  咕咕咕(待刷)


#32.Sort

题目:

  给出一个数组a,可以多次翻转某个区间,若翻转的区间为(l,r),则代价为r-l+1,求出在代价和<=20000000的情况下,将a数组变成不下降序列的所有翻转的操作的l和r

题解:

  一眼不会(这是假的NOIP模拟赛),膜了一发题解

  对于50%的只有0和1的点,显然直接归并排序做就可以了

  而对于另外的点,利用快排的思想,找到基准值,将<=基准值的作为一类,将>基准值的作为一类,然后也用归并就好了

  具体%%%

参考代码:

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<cmath>
#include<ctime>
using namespace std;
int a[51000];
bool check(int l,int r){for(int i=l;i<=r;i++) if(a[i]<a[i-1]) return false;return true;}
void Sort(int l,int r,int x)
{
    if(l==r) return ;
    int mid=(l+r)/2,p=l,q=r;
    Sort(l,mid,x);Sort(mid+1,r,x);
    while(p<=mid&&a[p]<=x) p++;
    while(mid<q&&a[q]>x) q--;
    if(p<=mid&&mid<q) printf("%d %d\n",p,q),reverse(a+p,a+q+1);
}
void solve(int l,int r)
{
    if(check(l,r)==true) return ;
    int mid=a[l+rand()%(r-l+1)];
    Sort(l,r,mid);
    for(int i=l;i<=r;i++) if(a[i]>mid){solve(l,i-1);solve(i,r);return ;}
    solve(l,r);
}
int main()
{
    srand(time(0));
    int n;
    scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    solve(1,n);
    printf("-1 -1\n");
    return 0;
}
#32

#33.bst

题目:

  给出一棵高度为n+1的满二叉树,共有2n个叶子节点,从左到右,叶子节点上的值分别为1到2n

  定义(i,j)为在二叉树上,从上到下第i层,从左到右第j个节点,我们称之为X2i-1+j(注意这不是节点编号,是该位置的节点)

  定义swap(k)为将Xk节点的左右子树交换

  先对于这棵二叉树有两种操作:

  1.输入l,r,相当于swap(l),swap(l+1)....swap(r)

  2.输入x,输出从左到右第x个叶子上的值

题解:

  我们可以把整棵树看成一张线段树,显然第二种操作的时候只要直接找到叶子节点就可以了

  对于翻转操作,就将l到r分解成不同高度层来处理,预处理每个位置节点的子树的最小的叶子节点和最大的叶子节点

  然后直接找到对应点打标记就行了

  有两种标记:

  1.翻转标记,表示当前节点是否被翻转过

  2.向下翻转标记,这是一个状压的状态,若二进制中第3位(即22)不为0,则表示当前子树中深度为3的点都要翻转自己的左右子树

参考代码:

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<algorithm>
using namespace std;
struct node
{
    int lc,rc,c,p,d;
    bool fz;
}tr[3100000];int trlen;
void bt(int l,int r,int d)
{
    trlen++;int now=trlen;
    tr[now].c=tr[now].p=0;
    tr[now].fz=false;tr[now].d=d;
    tr[now].lc=tr[now].rc=-1;
    if(l==r) tr[now].c=l;
    else
    {
        int mid=(l+r)/2;
        tr[now].lc=trlen+1;bt(l,mid,d+1);
        tr[now].rc=trlen+1;bt(mid+1,r,d+1);
    }
}
void update(int now)
{
    int lc=tr[now].lc,rc=tr[now].rc;
    if(lc!=-1)
    {
        tr[lc].p^=tr[now].p;
        if((tr[lc].p&(1<<tr[lc].d))==(1<<tr[lc].d)) tr[lc].fz^=1,tr[lc].p^=1<<tr[lc].d;
    }
    if(rc!=-1)
    {
        tr[rc].p^=tr[now].p;
        if((tr[rc].p&(1<<tr[rc].d))==(1<<tr[rc].d)) tr[rc].fz^=1,tr[rc].p^=1<<tr[rc].d;
    }
    tr[now].p=0;
}
void change(int now,int l,int r,int c,int ll,int rr)
{
    if(l==ll&&r==rr)
    {
        tr[now].p^=c;
        if((tr[now].p&(1<<tr[now].d))==(1<<tr[now].d)) tr[now].fz^=1,tr[now].p^=1<<tr[now].d;
        return ;
    }
    int lc=tr[now].lc,rc=tr[now].rc,mid=(ll+rr)/2;
    if(tr[now].p!=0) update(now);
    if(tr[now].fz==true) swap(lc,rc);
    if(r<=mid) change(lc,l,r,c,ll,mid);
    else if(l>mid) change(rc,l,r,c,mid+1,rr);
    else change(lc,l,mid,c,ll,mid),change(rc,mid+1,r,c,mid+1,rr);
}
int getd(int now,int x,int ll,int rr)
{
    if(ll==rr) return tr[now].c;
    int lc=tr[now].lc,rc=tr[now].rc,mid=(ll+rr)/2;
    if(tr[now].p!=0) update(now);
    if(tr[now].fz==true) swap(lc,rc);
    if(x<=mid) return getd(lc,x,ll,mid);
    else return getd(rc,x,mid+1,rr);
}
int n,L[3100000],R[3100000];
void dfs(int x)
{
    if(x*2>=(1<<(n+1))){L[x]=R[x]=(1<<n)+x-(1<<(n+1))+1;return ;}
    dfs(x*2);dfs(x*2+1);
    L[x]=L[x*2];R[x]=R[x*2+1];
}
int main()
{
    int q;
    scanf("%d%d",&n,&q);
    trlen=0;bt(1,1<<n,0);
    dfs(1);
    while(q--)
    {
        int t;
        scanf("%d",&t);
        if(t==1)
        {
            int l,r;
            scanf("%d%d",&l,&r);
            for(int i=1;i<=n+1;i++)
            {
                if(l>(1<<i)-1) continue;
                if(r<(1<<(i-1))) break;
                int x=max(l,(1<<(i-1))),y=min(r,(1<<i)-1);
                change(1,L[x],R[y],1<<(i-1),1,(1<<n));
            }
        }
        else
        {
            int x;
            scanf("%d",&x);
            printf("%d\n",getd(1,x,1,(1<<n)));
        }
    }
    return 0;
}
#33

#34.palindrome

题目:

  给出一个字符串,将字符串拆分,合法拆分得到的字符串A1,A2.....Ak必须满足A1=Ak,A2=Ak-1......

  求出最多将字符串拆分成多少个字符串

题解:

  直接做,首先l,r表示当前处理的左端点和右端点,一开始l=1,r=len

  如果st[l]==st[r]的话,l==r时ans++,否则ans+=2

  如果遇到st[l]!=st[r]的话,就l,r都往里面缩,知道找到当前l到r的字符串中第一个前缀等于后缀的位置,然后分情况加ans就行了

  找前缀后缀时只用hash来判断是否相同,这样就不用重新扫一遍判断是否相等了

参考代码:

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<algorithm>
using namespace std;
typedef unsigned long long ULL;
ULL P=131;
char st[1100000];
int main()
{
    int T;
    scanf("%d",&T);
    while(T--)
    {
        scanf("%s",st+1);
        int len=strlen(st+1);
        int l=1,r=len,ans=0;
        while(l<=r)
        {
            while(l<=r&&st[l]==st[r]) ans+=1+(l!=r),l++,r--;
            if(l>r) break;
            ULL s1=st[l]-'a'+1,s2=st[r]-'a'+1,p=131;
            while(s1!=s2&&l<=r)
            {
                if(l+1>r-1) break;
                s1=s1*P+st[l+1]-'a'+1;
                s2=s2+p*(st[r-1]-'a'+1);
                p*=P;
                l++;r--;
            }
            if(s1==s2) ans+=2;
            else ans++;
            l++;r--;
        }
        printf("%d\n",ans);
    }
    return 0;
}
#34

#35.string

题目:

  有一个长度为n的字符串,只由ABCD四个字母组成

  有m种魔法操作,每种操作输入Xi字符串和Yi字符串

  可以对任意一个长度为n的字符串进行两种操作:

  1.将字符串中相邻位置的字符交换

  2.如果有一个子串为Xi,则可以将这个子串转换成Yi

  保证每次操作后字符串的长度仍然为n

  现要构造一个长度为n的字符串,能够对其进行无数次操作,使得每次操作后得到的不同的字符串的个数最大

题解:

  终于有点像NOIP的题了。。

  显然不能直接做,我们需要转化一下

  对于一个字符串,实际上我们只关心它每个字母的个数就可以了,因为当字母个数相同时,两个字符串之间可以通过第一种操作来互相转化

  那么我们就只用id[a][b][c]表示有a个A,b个B,c个C,n-a-b-c个D的字符串的离散坐标,d[i]表示离散坐标为i的字符串通过第一种操作能得到的不同字符串数

  显然d数组可以用组合数学求出来,如:n=10,a=2,b=3,c=4时,不同的字符串数为10!/2!/3!/4!/1!

  然后对于第二种操作,就将所有相关的字符串连边就行了

  tarjan缩点,然后拓扑排序,更新答案就行了

  PS:要高精度,不过苟一点的话,可以用int128

参考代码:

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<cmath>
#include<queue>
using namespace std;
int s[210];
struct up
{
    int a[210],len;
    up()
    {
        len=0;
        memset(a,0,sizeof(a));
    }
    friend up operator * (up a,int b)
    {
        up c;c.len=a.len;
        for(int i=1;i<=c.len;i++) c.a[i]=a.a[i]*b;
        for(int i=1;i<=c.len;i++){c.a[i+1]+=c.a[i]/10;c.a[i]%=10;}
        int i=c.len;
        while(c.a[i+1]!=0)
        {
            i++;
            c.a[i+1]+=c.a[i]/10;
            c.a[i]%=10;
        }
        c.len=i;
        return c;
    }
    friend up operator + (up a,up b)
    {
        up c;c.len=max(a.len,b.len);
        for(int i=1;i<=c.len;i++) c.a[i]=a.a[i]+b.a[i];
        for(int i=1;i<=c.len;i++){c.a[i+1]+=c.a[i]/10;c.a[i]%=10;}
        int i=c.len;
        while(c.a[i+1]!=0)
        {
            i++;
            c.a[i+1]+=c.a[i]/10;
            c.a[i]%=10;
        }
        c.len=i;
        return c;
    }
    friend up operator / (up a,int b)
    {
        int d=0,len=a.len,cnt=0;
        while(len!=0)
        {
            d=d*10+a.a[len];
            if(cnt!=0||d/b!=0) s[++cnt]=d/b;
            d%=b;
            len--;
        }
        up c;c.len=cnt;
        for(int i=1;i<=cnt;i++) c.a[i]=s[cnt-i+1];
        if(cnt==0) c.len=1,c.a[1]=0;
        return c;
    }
}d[41000],S[41000],f[41000];
up Max(up a,up b)//-1 0 1
{
    if(a.len>b.len) return a;
    if(a.len<b.len) return b;
    for(int i=a.len;i>=1;i--)
    {
        if(a.a[i]>b.a[i]) return a;
        if(a.a[i]<b.a[i]) return b;
    }
    return a;
}
struct node
{
    int x,y,next;
}a[5100000];int len,last[41000];
void ins(int x,int y){a[++len]=(node){x,y,last[x]};last[x]=len;}
char st[51];
int id[51][51][51],tot,n;
void count(int &a,int &b,int &c,int &d)
{
    a=b=c=d=0;
    for(int i=1;i<=n;i++)
    {
        if(st[i]=='A') a++;
        if(st[i]=='B') b++;
        if(st[i]=='C') c++;
        if(st[i]=='D') d++;
    }
}
void pre()
{
    tot=0;
    up p,pp;p.a[1]=1;p.len=1;
    for(int i=1;i<=n;i++) p=p*i;
    for(int a=0;a<=n;a++)
    {
        for(int b=0;a+b<=n;b++)
        {
            for(int c=0;a+b+c<=n;c++)
            {
                id[a][b][c]=++tot;pp=p;
                for(int i=1;i<=a;i++) pp=pp/i;
                for(int i=1;i<=b;i++) pp=pp/i;
                for(int i=1;i<=c;i++) pp=pp/i;
                for(int i=1;i<=n-a-b-c;i++) pp=pp/i;
                d[tot]=pp;
            }
        }
    }
}
int dfn[41000],low[41000],df;
int belong[41000],cnt;
bool v[41000];int sta[41000],tp;
void dfs(int x)
{
    dfn[x]=low[x]=++df;
    sta[++tp]=x;v[x]=true;
    for(int k=last[x];k;k=a[k].next)
    {
        int y=a[k].y;
        if(dfn[y]==0)
        {
            dfs(y);
            low[x]=min(low[x],low[y]);
        }
        else if(v[y]==true) low[x]=min(low[x],dfn[y]);
    }
    if(low[x]==dfn[x])
    {
        int i;cnt++;
        S[cnt].len=1;S[cnt].a[1]=0;
        do
        {
            i=sta[tp--];
            belong[i]=cnt;
            v[i]=false;
            S[cnt]=S[cnt]+d[i];
        }while(i!=x);
    }
}
int ru[41000];
int X[5100000],Y[5100000];
queue<int> q; 
int main()
{
    freopen("a.in","r",stdin);
    freopen("vio.out","w",stdout);
    int m;
    scanf("%d%d",&n,&m);
    pre();
    len=0;memset(last,0,sizeof(last));
    int A1,B1,C1,D1,A2,B2,C2,D2;
    for(int i=1;i<=m;i++)
    {
        memset(st,0,sizeof(st));
        scanf("%s",st+1);count(A1,B1,C1,D1);
        scanf("%s",st+1);count(A2,B2,C2,D2);
        if(A1==A2&&B1==B2&&C1==C2&&D1==D2) continue;
        for(int A=A1;A<=n-D1;A++)
        {
            for(int B=B1;A+B<=n-D1;B++)
            {
                for(int C=C1;A+B+C<=n-D1;C++)
                {
                    ins(id[A][B][C],id[A-A1+A2][B-B1+B2][C-C1+C2]);
                }
            }
        }
    }
    memset(dfn,0,sizeof(dfn));
    memset(v,false,sizeof(v));
    df=cnt=tp=0;
    for(int i=1;i<=tot;i++) if(dfn[i]==0) dfs(i);
    memset(ru,0,sizeof(ru));
    int blen=0;
    for(int i=1;i<=len;i++)
    {
        int bx=belong[a[i].x],by=belong[a[i].y];
        if(bx!=by)
        {
            ru[by]++;blen++;
            X[blen]=bx;Y[blen]=by;
        }
    }
    len=0;memset(last,0,sizeof(last));
    for(int i=1;i<=blen;i++) ins(X[i],Y[i]);
    up ans;ans.len=1;ans.a[1]=0;
    for(int i=1;i<=cnt;i++)
    {
        f[i].len=1;f[i].a[1]=0;
        ans=Max(ans,S[i]);
        if(ru[i]==0) q.push(i);
    }
    while(q.empty()==0)
    {
        int x=q.front();q.pop();
        for(int k=last[x];k;k=a[k].next)
        {
            int y=a[k].y;
            f[y]=Max(f[y],f[x]+S[x]);
            ans=Max(f[y]+S[y],ans);
            ru[y]--;if(ru[y]==0) q.push(y);
        }
    }
    for(int i=ans.len;i>=1;i--) printf("%d",ans.a[i]);
    printf("\n");
    return 0;
}
#35

#36.列队

题目:

  给出一个n*m的矩阵,保证里面的数互不相同且一定在[1,n*m]的范围中

  给出q组询问,每组询问输入x,y,求出有多少个数,在它所在的排中是第x高,所在的列中是第y高的

题解:

  直接离线求出所有值的排和列的排名,然后记录每对x,y的值

  然后询问就直接输出就行了

参考代码:

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<algorithm>
using namespace std;
int P[2][1100000];
int a[1100][1100];
int ans[1100][1100];
int h[1100];
int main()
{
    int n,m,q;
    scanf("%d%d%d",&n,&m,&q);
    for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) scanf("%d",&a[i][j]);
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=m;j++) h[j]=a[i][j];
        sort(h+1,h+m+1);
        for(int j=1;j<=m;j++) P[0][h[j]]=m-j+1;
    }
    for(int i=1;i<=m;i++)
    {
        for(int j=1;j<=n;j++) h[j]=a[j][i];
        sort(h+1,h+n+1);
        for(int j=1;j<=n;j++) P[1][h[j]]=n-j+1;
    }
    for(int i=1;i<=n*m;i++)
    {
        ans[P[0][i]][P[1][i]]++;
    }
    while(q--)
    {
        int x,y;
        scanf("%d%d",&x,&y);
        printf("%d\n",ans[x][y]);
    }
    return 0;
}
#36

#37.染色

题目:

  有n个格子,m种颜色,要求将n个格子染色,使得相邻m个格子必定至少有两个格子的颜色相同,求出染色方案数

题解:

  计数DP,很显然

  设f[i][j]为染到第i个格子,且后j个格子颜色互不相同,第i-j个格子的颜色必定与后j个格子中的其中一个格子的颜色相同

  显然如果我们选择与后j个格子都不同颜色的话,就得到转移方程:f[i+1][j+1]+=f[i][j]*(m-j)(j+1<m)

  如果我们选择与后j个格子中其中一个格子的颜色相同的话,我们就可以得到:f[i+1][k]+=f[i][j](1<=k<=j且j<m)

  因为即使我们不确定我们选择与后j个格子中的哪一个格子颜色相同,但是我们可以知道一定是后1到j的位置上的格子,所以直接转移就行了

  显然直接做要O(n3),实际上可以差分,因为第一个转移是单点增加,第二个转移是区间增加,直接差分,最后求前缀和就行了

参考代码:

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<algorithm>
using namespace std;
typedef long long LL;
LL f[5100][5100],s[5100];
int main()
{
    int n,m;LL Mod;
    scanf("%d%d%lld",&n,&m,&Mod);
    if(m==1){printf("0\n");return 0;}
    memset(f,0,sizeof(f));
    f[1][1]=m;
    for(int i=1;i<n;i++)
    {
        memset(s,0,sizeof(s));
        for(int j=1;j<=min(i,m);j++)
        {
            if(j<m) s[1]=(s[1]+f[i][j])%Mod,s[j+1]=(s[j+1]-f[i][j]+Mod)%Mod;
            if(j+1<m) s[j+1]=(s[j+1]+f[i][j]*LL(m-j)%Mod)%Mod,s[j+2]=(s[j+2]-f[i][j]*LL(m-j)%Mod+Mod)%Mod;
        }
        for(int j=1;j<=m;j++) s[j]=(s[j]+s[j-1])%Mod,f[i+1][j]=s[j];
    }
    LL ans=0;
    for(int i=1;i<m;i++) ans=(ans+f[n][i])%Mod;
    printf("%lld\n",ans);
    return 0;
}
#37

#38.游戏

题目:

  有n个人,每个人有初始经验

  给出等级划分a[i],若一个人的经验为x,则找到最大的a[i]<=x,则这个人的等级为i

  如果x<a[1],则等级为0

  有三种操作:

  1.将l到r的人的经验都增加x

  2.将第p个人的经验改为x

  3.求出l到r的人的等级之和

题解:

  一开始还不太会做

  结果发现等级数量<=10,直接就上线段树

  因为每个叶子节点通过增加经验而改变等级的次数不超过10

  所以对于一个区间,我们维护区间内离升级最近的值,如果加上当前的x会升级,则暴力修改,否则直接打上lazy标记

  然后维护区间等级和以及区间内离升级最近的值就可以了

  复杂度可观

  注意要开long long,题目没给数据范围,WA了一遍才知道

参考代码:

#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<cstdlib>
using namespace std;
typedef long long LL;
struct trnode
{
    int l,r,lc,rc,d;LL c,lazy;
}tr[210000];int trlen;
int s[110000],m;
LL a[21];
int geta(LL d)
{
    int l=0,r=m,ans;
    while(l<=r)
    {
        int mid=(l+r)>>1;
        if(d>=a[mid]) l=mid+1,ans=mid;
        else r=mid-1;
    }
    return ans;
}
void follow(int now)
{
    int lc=tr[now].lc,rc=tr[now].rc;
    tr[now].c=min(tr[lc].c,tr[rc].c);
    tr[now].d=tr[lc].d+tr[rc].d;
}
void bt(int l,int r)
{
    trlen++;int now=trlen;
    tr[now].l=l;tr[now].r=r;tr[now].c=tr[now].d=0;
    tr[now].lc=tr[now].rc=-1;tr[now].lazy=0;
    if(l==r)
    {
        tr[now].d=geta(s[l]);
        tr[now].c=a[tr[now].d+1]-s[l];
    }
    else
    {
        int mid=(l+r)/2;
        tr[now].lc=trlen+1;bt(l,mid);
        tr[now].rc=trlen+1;bt(mid+1,r);
        follow(now);
    }
}
void update(int now)
{
    int lc=tr[now].lc,rc=tr[now].rc;
    if(lc!=-1)
    {
        tr[lc].c-=tr[now].lazy;
        tr[lc].lazy+=tr[now].lazy;
    }
    if(rc!=-1)
    {
        tr[rc].c-=tr[now].lazy;
        tr[rc].lazy+=tr[now].lazy;
    }
    tr[now].lazy=0;
}
void bf(int now,LL d)
{
    if(tr[now].c>d)
    {
        tr[now].c-=d;
        tr[now].lazy+=d;
        return ;
    }
    if(tr[now].l==tr[now].r)
    {
        tr[now].d++;
        tr[now].c=a[tr[now].d+1]-(a[tr[now].d]+d-tr[now].c);
        return ;
    }
    int lc=tr[now].lc,rc=tr[now].rc;
    if(tr[now].lazy!=0) update(now);
    if(lc!=-1) bf(lc,d);
    if(rc!=-1) bf(rc,d);
    follow(now);
}
void add(int now,int l,int r,LL d)
{
    if(tr[now].l==l&&tr[now].r==r)
    {
        bf(now,d);
        return ;
    }
    int lc=tr[now].lc,rc=tr[now].rc,mid=(tr[now].l+tr[now].r)/2;
    if(tr[now].lazy!=0) update(now);
    if(r<=mid) add(lc,l,r,d);
    else if(l>mid) add(rc,l,r,d);
    else add(lc,l,mid,d),add(rc,mid+1,r,d);
    follow(now);
}
void change(int now,int x,LL d)
{
    if(tr[now].l==tr[now].r)
    {
        tr[now].d=geta(d);
        tr[now].c=a[tr[now].d+1]-d;
        return ;
    }
    int lc=tr[now].lc,rc=tr[now].rc,mid=(tr[now].l+tr[now].r)/2;
    if(tr[now].lazy!=0) update(now);
    if(x<=mid) change(lc,x,d);
    else change(rc,x,d);
    follow(now);
}
int getsum(int now,int l,int r)
{
    if(tr[now].l==l&&tr[now].r==r) return tr[now].d;
    int lc=tr[now].lc,rc=tr[now].rc,mid=(tr[now].l+tr[now].r)/2;
    if(tr[now].lazy!=0) update(now);
    if(r<=mid) return getsum(lc,l,r);
    else if(l>mid) return getsum(rc,l,r);
    else return getsum(lc,l,mid)+getsum(rc,mid+1,r);
}
int main()
{
    int n,q;
    scanf("%d%d%d",&n,&m,&q);a[0]=0;
    for(int i=1;i<=m;i++) scanf("%lld",&a[i]);
    a[m+1]=1LL<<62;
    for(int i=1;i<=n;i++) scanf("%lld",&s[i]);
    bt(1,n);
    while(q--)
    {
        int t;scanf("%d",&t);
        if(t==1)
        {
            int l,r;LL x;
            scanf("%d%d%lld",&l,&r,&x);
            add(1,l,r,x);
        }
        if(t==2)
        {
            int p;LL x;
            scanf("%d%lld",&p,&x);
            change(1,p,x);
        }
        if(t==3)
        {
            int l,r;
            scanf("%d%d",&l,&r);
            printf("%d\n",getsum(1,l,r));
        }
    }
    return 0;
}
#38

#39.子图

题目:

  给出n个点m条边的图,要求删掉一些边,使得图中任意一些点之间相连的边小于这些点的数量

  边有边权,删掉一条边需要花费同等于边权的代价,求出最小代价

题解:

  这不就是求最大生成树吗(花里胡哨的题面)

  将所有边的边权和-最大生成树的边权和即为答案

参考代码:

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<algorithm>
using namespace std;
typedef long long LL;
inline int read()
{
    int x=0,f=1;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();}
    return x*f;
}
struct edge
{
    int x,y,d;
}e[510000];
bool cmp(edge n1,edge n2){return n1.d>n2.d;}
int fa[510000];
int findfa(int x)
{
    if(fa[x]!=x) fa[x]=findfa(fa[x]);
    return fa[x];
}
int main()
{
    int n=read(),m=read();LL ans=0;
    for(int i=1;i<=m;i++) e[i].x=read(),e[i].y=read(),e[i].d=read(),ans+=e[i].d;
    sort(e+1,e+m+1,cmp);
    for(int i=1;i<=n;i++) fa[i]=i;
    int tot=0;
    for(int i=1;i<=m;i++)
    {
        int fx=findfa(e[i].x),fy=findfa(e[i].y);
        if(fx!=fy)
        {
            fa[fx]=fy;
            ans-=e[i].d;
            tot++;if(tot==n-1) break;
        }
    }
    printf("%lld\n",ans);
    return 0;
}
#39

#40.Erlang

题目:

  给出n个任务,每个任务有ki个子任务,编号在[1,2*105]之间,有可能重复

  可以选择一个任务,该任务会随机抽出没有执行过的子任务来执行

  如果当前执行的子任务在之前就已经执行过的话,就停止操作

题解:

  如果只针对一个任务来抽,答案就是不同的子任务数量+1

  而如果不是针对一个任务,那也肯定只针对两个任务

  显然一个子任务在一个任务中被抽到的最坏情况为这个任务的总子任务-等于这个子任务的数量+1

  我们假设先选择其中一个任务,设f[i]为在其他任务中,抽到当前任务的第i个子任务的最小次数

  然后将f数组从大到小排名,如果f[i]从大到小的排名为j,则最坏情况对答案的贡献就为f[i]+j

  因为如果超过了j次,那么之前一定抽到了比f[i]更小的

参考代码:

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<algorithm>
#include<vector>
using namespace std;
vector<int> v[510000],D[510000];
int a[510000],f1[510000],f2[510000],k[510000];
int ff[510000],d[510000];
int main()
{
    int n;
    scanf("%d",&n);
    int ans=1<<30;
    memset(f1,63,sizeof(f1));
    memset(f2,63,sizeof(f2));
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&k[i]);
        for(int j=1;j<=k[i];j++) scanf("%d",&a[j]),d[a[j]]++;
        a[0]=0;a[k[i]+1]=0;
        sort(a+1,a+k[i]+1);
        int s=0;
        for(int j=1;j<=k[i];j++) if(a[j-1]!=a[j]) s++;
        if(s!=k[i]) ans=min(ans,s+1);
        for(int j=1;j<=k[i];j++)
        {
            if(a[j-1]!=a[j])
            {
                v[i].push_back(a[j]);
                D[i].push_back(d[a[j]]);
                if(f1[a[j]]>k[i]-d[a[j]]+1)
                {
                    swap(f1[a[j]],f2[a[j]]);
                    f1[a[j]]=k[i]-d[a[j]]+1;
                }
                else if(f2[a[j]]>k[i]-d[a[j]]+1) f2[a[j]]=k[i]-d[a[j]]+1;
            }
            d[a[j]]=0;
        }
    }
    for(int i=1;i<=n;i++)
    {
        int len=v[i].size();
        for(int j=0;j<len;j++)
        {
            ff[j+1]=(k[i]-D[i][j]+1)==f1[v[i][j]]?f2[v[i][j]]:f1[v[i][j]];
        }
        sort(ff+1,ff+len+1);
        for(int j=1;j<=len;j++) if(ff[j]!=f1[0]) ans=min(ans,ff[j]+len-j+1);
    }
    if(ans==(1<<30)) ans=-1;
    printf("%d\n",ans);
    return 0;
}
#40

#41.最短路

题目:

  咕咕咕(待刷)


#42.queen

题目:

  有一个n*n的棋盘,棋盘上有m个皇后(保证任意两个皇后不在同一个格子上)

  已知皇后会攻击上下左右,左上、左下、右上、右下八个方向分别离当前皇后最近的皇后

  求出分别被攻击了i次的皇后的个数

题解:

  对于向左上角的直线,可以将x-y作为下标,对于向右上角的直线,可以将x+y作为下标

  然后求出两种直线的能取到的皇后的最大的x值,并记录同行的最大y值,同列的最大x值

  最后分别判断取值就行了

参考代码:

#include<cstdio>
#include<cstdlib>
#include<cmath>
#include<cstring>
#include<algorithm>
using namespace std;
int mn1[210000],mx1[210000],mn2[210000],mx2[210000];
int mxX[210000],mnX[210000],mxY[210000],mnY[210000];
int h[210000];
struct sit
{
    int x,y;
}a[110000];
int d[110000],ans[1100000];
int main()
{
    int n,m;
    scanf("%d%d",&n,&m);
    memset(mn1,63,sizeof(mn1));
    memset(mn2,63,sizeof(mn2));
    memset(mnX,63,sizeof(mnX));
    memset(mnY,63,sizeof(mnY));
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d",&a[i].x,&a[i].y);
        int d1=a[i].x-a[i].y+n,d2=a[i].x+a[i].y;
        mn1[d1]=min(mn1[d1],a[i].x);
        mx1[d1]=max(mx1[d1],a[i].x);
        mn2[d2]=min(mn2[d2],a[i].x);
        mx2[d2]=max(mx2[d2],a[i].x);
        mnX[a[i].x]=min(mnX[a[i].x],a[i].y);
        mxX[a[i].x]=max(mxX[a[i].x],a[i].y);
        mnY[a[i].y]=min(mnY[a[i].y],a[i].x);
        mxY[a[i].y]=max(mxY[a[i].y],a[i].x);
    }
    for(int i=1;i<=m;i++)
    {
        int d1=a[i].x-a[i].y+n,d2=a[i].x+a[i].y;
        if(mn1[d1]!=a[i].x) d[i]++;
        if(mx1[d1]!=a[i].x) d[i]++;
        if(mn2[d2]!=a[i].x) d[i]++;
        if(mx2[d2]!=a[i].x) d[i]++;
        if(mnX[a[i].x]!=a[i].y) d[i]++;
        if(mxX[a[i].x]!=a[i].y) d[i]++;
        if(mnY[a[i].y]!=a[i].x) d[i]++;
        if(mxY[a[i].y]!=a[i].x) d[i]++;
    }
    for(int i=1;i<=m;i++) ans[d[i]]++;
    for(int i=0;i<8;i++) printf("%d ",ans[i]);
    printf("%d\n",ans[8]);
    return 0;
}
#42

#43.ladder

题目:

  咕咕咕(待刷)


#44.color

题目:

  给出n个数,m个询问,数的范围为[1,k],一个数T

  每组询问给出l,r,求出l到r中出现次数=T的数的个数

题解:

  离线,直接上莫队,实时记录答案和每个数出现的次数,4980ms水过去。。。。

参考代码:

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<algorithm>
using namespace std;
inline int read()
{
    int x=0,f=1;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();}
    return x*f;
}
int belong[510000];
struct query
{
    int l,r,id;
}Q[510000];
bool cmp(query n1,query n2){return belong[n1.l]==belong[n2.l]?n1.r<n2.r:belong[n1.l]<belong[n2.l];}
int sum[510000],col[510000],d[510000];
int main()
{
    int n=read(),m=read(),k=read(),T=read();
    for(int i=1;i<=n;i++) col[i]=read();
    int block=sqrt(n);
    for(int i=1;i<=n;i++) belong[i]=(i-1)/block+1;
    block=belong[n];
    for(int i=1;i<=m;i++) Q[i].l=read(),Q[i].r=read(),Q[i].id=i;
    sort(Q+1,Q+m+1,cmp);
    int l=1,r=0,ans=0;
    for(int i=1;i<=m;i++)
    {
        while(r<Q[i].r)
        {
            r++;
            if(sum[col[r]]==T) ans--;
            sum[col[r]]++;
            if(sum[col[r]]==T) ans++;
        }
        while(r>Q[i].r)
        {
            if(sum[col[r]]==T) ans--;
            sum[col[r]]--;
            if(sum[col[r]]==T) ans++;
            r--;
        }
        while(l<Q[i].l)
        {
            if(sum[col[l]]==T) ans--;
            sum[col[l]]--;
            if(sum[col[l]]==T) ans++;
            l++;
        }
        while(l>Q[i].l)
        {
            l--;
            if(sum[col[l]]==T) ans--;
            sum[col[l]]++;
            if(sum[col[l]]==T) ans++;
        }
        d[Q[i].id]=ans;
    }
    for(int i=1;i<=m;i++) printf("%d\n",d[i]);
    return 0;
}
#44

#45.count

题目:

  有一个长度为n+1的序列,每个数不超过n且n以内每个正整数至少出现一次

  求出分别长度为i的子序列的个数

题解:

  显然有且仅有两个位置的值相等,且对答案会造成影响,假设两个位置为x,y(x<y)

  先算$C_{n+1}^{i}$,答案肯定比这个小,yy一下就会发现,实际上答案就是$C_{n+1}^{i}-C_{n+1-y+x-1}^{i-1}$

  这个东西就不用多说了吧。。

参考代码:

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<algorithm>
using namespace std;
typedef long long LL;
LL Mod=1e9+7;
LL p_mod(LL a,LL b)
{
    LL ans=1;
    while(b!=0)
    {
        if(b%2==1) ans=ans*a%Mod;
        a=a*a%Mod;b>>=1;
    }
    return ans;
}
LL jc[110000],ny[110000];
LL C(int n,int m){return jc[n]*ny[m]%Mod*ny[n-m]%Mod;}
int v[110000],a[110000];
int main()
{
    int n;
    scanf("%d",&n);
    jc[0]=1;ny[0]=1;
    for(int i=1;i<=n+1;i++) jc[i]=jc[i-1]*(LL)i%Mod,ny[i]=p_mod(jc[i],Mod-2);
    memset(v,0,sizeof(v));int x,y;
    for(int i=1;i<=n+1;i++)
    {
        scanf("%d",&a[i]);
        if(v[a[i]]!=0) x=v[a[i]],y=i;
        v[a[i]]=i;
    }
    printf("%d\n",n);
    for(int i=2;i<=n+1;i++)
    {
        LL d=C(n+1,i);
        if(n+1-y+x-1>=i-1) d=(d-C(n+1-y+x-1,i-1)+Mod)%Mod;
        printf("%lld\n",d);
    }
    return 0;
}
#45

#46.delete

题目:

  给出一个有n个数的序列,要求删掉恰好k个数(右边的数往左移),求出最多有多少个数与它的下标相同,即a[i]=i的个数

题解:

  显然如果i位置满足条件,j位置也满足条件(j<i)

  那么肯定j<i,a[j]<a[i],且i和j中间的数必须>=a[i]-a[j],即i-j>=a[i]-a[j],即i-a[i]>=j-a[j]

  那就直接按照a来排序,按顺序求值,明显不能达到要求的跳过

  设f[i]为排序后的顺序为i的数作为最后一个满足条件的情况下,答案的最大值

  用树状数组维护区间最大值就行了

参考代码:

#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<cstdlib>
using namespace std;
struct node
{
    int c,d,id;
}P[1100000];
bool cmp(node n1,node n2){return n1.c<n2.c;}
int k,a[1100000];
int lowbit(int x){return x&-x;}
void change(int x,int d)
{
    while(x<=k+1)
    {
        a[x]=max(a[x],d);
        x+=lowbit(x);
    }
}
int getmax(int x)
{
    int ans=0;
    while(x!=0)
    {
        ans=max(ans,a[x]);
        x-=lowbit(x);
    }
    return ans;
}
int f[1100000];
int main()
{
    int n;
    scanf("%d%d",&n,&k);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&P[i].c);
        P[i].d=i-P[i].c+1;
        P[i].id=i;
    }
    sort(P+1,P+n+1,cmp);
    memset(a,0,sizeof(a));
    int lst=0;
    for(int i=1;i<=n;i++)
    {
        if(P[i].c!=P[i-1].c)
        {
            for(int j=lst+1;j<=i-1;j++)
            {
                if(P[j].d<=0||P[j].d>k+1||P[j].c>n-k) continue;
                change(P[j].d,f[j]);
            }
            lst=i-1;
        }
        if(P[i].d<=0||P[i].d>k+1||P[i].c>n-k) continue;
        f[i]=getmax(P[i].d)+1;
    }
    int ans=0;
    for(int i=1;i<=n;i++)
    {
        if(P[i].d<=0||P[i].d>k+1||P[i].c>n-k) continue;
        if(k-(P[i].d-1)<=n-P[i].id) ans=max(ans,f[i]);
    }
    printf("%d\n",ans);
    return 0;
}
#46

#47.power

题目:

  咕咕咕(待刷)

转载于:https://www.cnblogs.com/Never-mind/p/9894489.html

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值