NOI.AC: NOIP2018 全國模擬賽習題練習

閒談:

  最後一個星期仍是不浪了,作一下模擬賽(仍是有點小虛)
html


#30.candynode

題目:

  有一我的想買糖吃,有兩家商店A,B,A商店中第i個糖果的愉悅度爲Ai,B商店中第i個糖果的愉悅度爲Bi
c++

  給出n,W,表示每一個商店都有n個糖果且兩個商店的每一個糖果的價格都是W數組

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

  其中Sa表示在A商店買的糖果的愉悅度之和,Sb表示在B商店中買的糖果的愉悅度之和,D表示總共在兩家商店買的糖果數spa

題解:

  直接亂搞,貪心想想,每次取糖果確定先從愉悅度大的取爲優code

  那麼咱們只要在取糖果的時候,一旦有一個商店的愉悅度和比較大,則買另外一個商店的糖果,由於這樣就能增大min(Sa,Sb)htm

  而後在過程當中判斷一下其餘狀況就好了blog

參考代碼:

#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

題目:

咕咕咕(待刷)

相關文章
相關標籤/搜索