1. 程式人生 > >NOIP模擬2017.9.19 總結+心得

NOIP模擬2017.9.19 總結+心得

這裡寫圖片描述
世界真的很大
今天真的虧,虧大了,眼睜睜放棄正解。。
恍然大悟時為時已晚

看題先:

1.NYG的揹包

【問題描述】
扎扙扇有一個神奇的揹包,每放進去一個物品,揹包的體積就會變大。
也就是說,每放進一個物品,揹包會被佔用一定的體積,但是緊接著揹包的總體積又
會增大一定的值(注意是在放入物品後背包總體積才增大)。
扎扙扇發覺這個揹包十分好用,於是不由自主地想到了一個問題。
現在給出揹包初始容量V以及n個物品,每一個物品兩個值a; b,分別表示物品所佔體積
和放入揹包後背包增大的體積。
扎扙扇想知道能否把所有物品裝進去?
因為扎扙扇比較老實,這麼簡單的問題自然裝作不會做的樣子。
於是他來請教你。
【輸入格式】
從檔案扢扡扣掃擾扡扣掃戮擴扮中讀入資料
輸入檔案包含多組資料。
第一行一個數扔表示資料組數。
對於每組資料,第一行兩個數n; h,接下來扮行每行兩個數a; b表示物品所佔體積和放入
揹包後背包增大的體積。
【輸出格式】
輸出到檔案扢扡扣掃擾扡扣掃戮扯扵扴中
對於每一組資料,如果能把所有物品裝進去,輸出戢扙扥扳戢,否則輸出戢扎扯戢(不包含引
號)

這道題一眼就是貪心,看一下資料範圍,每一次大概就只支援一次sort的時間,更加確定我貪心的想法
然後仔細一想,這個V好像是沒什麼用的。
為什麼這麼說呢?是因為對於一堆給定的物品,我們都是按照最優策略去放,就是說最優策略是一定的,不管V是多少,我們都是按照固定的順序去放東西,V的大小隻是決定策略能不能成功而已
然後我們就只需要決定一下對於給定的物品裡面,放入的順序

考慮比較兩個物品誰先放的時候,如果兩個都是使得放入之後增加的,那麼肯定是讓a小的先放。同理都是放入後使得空間變小的,肯定是讓b大的先放,那一個增加一個減少肯定是讓增加的先放

完整程式碼:

#include<stdio.h>
#include<iostream> #include<algorithm> using namespace std; typedef long long dnt; struct obj { dnt x,y; }a[1000010]; int n,T; dnt w; bool cmp(const obj &a , const obj &b) { if(a.y-a.x>=0 && b.y-b.x>=0) return a.x<b.x; if(a.y-a.x<0 && b.y-b.x<0
) return a.y>b.y; return a.y-a.x>=0; } int main() { freopen("backpack.in","r",stdin); freopen("backpack.out","w",stdout); scanf("%d",&T); while(T--) { cin >> n >> w; for(int i=1;i<=n;i++) scanf("%I64d%I64d",&a[i].x,&a[i].y); sort(a+1,a+n+1,cmp); int flag=1; for(int i=1;i<=n;i++) { if(a[i].x>w) { flag=0; break ; } w-=a[i].x,w+=a[i].y; } if(flag) printf("Yes\n"); else printf("No\n"); } return 0; } /* 1 3 5 3 1 4 8 8 3 */ /* 3 7 9269 21366 1233 7178 23155 16679 23729 15062 28427 939 6782 24224 9306 22778 13606 5 22367 17444 5442 16452 30236 14893 24220 31511 13634 4380 29422 7 18700 25935 4589 24962 9571 26897 14982 20822 2380 21103 12648 32006 22912 23367 20674 */

2.NYG的動態數點

【問題描述】
扎扙扇是一個善於思考的好學生。
於是扎扙扇想往他的揹包中放序列。
某一天扎扙扇得到了一個長度為n的序列faig。然後扎扙扇說,如果對於一段區間扛L;R扝,
存在L k R使得8i 2 扛L;R扝都有akjai,我們認為它有價值,價值為R �� L(若不滿足條
件則沒有價值)。
現在扎扙扇想知道所有區間中價值最大為多少,最大價值的區間有多少個,以及這些區
間分別是什麼。
【輸入格式】
從檔案擾扯擴扮扴戮擴扮中讀入資料
第一行一個整數n。
第二行n個整數ai。
【輸出格式】
輸出到檔案擾扯擴扮扴戮扯扵扴中
第一行兩個整數,扮扵扭和扶扡揚,分別表示價值最大的區間的個數以及最大價值。
第二行扮扵扭個整數,按升序輸出每一個最大價值區間的扌。

這道題。。真的是血虧
當時想了一會兒沒想出來。。就先寫了一個n^3的大暴力,暴力列舉區間,然後直接check一下區間裡德gcd在區間裡存不存在
然後發現區間gcd可以用線段樹得到,區間值域存在與否可以用主席數得到,由於gcd本身是log的,這樣O(n)check就變成了log^2,總複雜度就是nlog^2,60分到手

60程式碼:

#include<stdio.h>
#include<vector>
#include<algorithm>
using namespace std;

int gcd(int a,int b)
{
    return b==0 ? a : gcd(b,a%b);
}

struct Node
{
    int gd;
    Node *ls,*rs;
    void update()
    {
        gd=gcd(ls->gd,rs->gd);
    }
}pol[1000010],*tait=pol,*root1;

struct node
{
    int sum;
    node *ls,*rs;
}pool[1000010],*tail=pool,*root[300010],*null;

vector <int> G;

int n,a[300010],ans=0,bns,flag=0;

node* newnode()
{
    node *nd=++tail;
    nd->ls=nd->rs=null;
    nd->sum=0;
    return nd;
}

Node *build(int lf,int rg)
{
    Node *nd=++tait;
    if(lf==rg)
    {
        nd->gd=a[lf];
        nd->ls=nd->rs=0;
        return nd;
    }
    int mid=(lf+rg)>>1;
    nd->ls=build(lf,mid);
    nd->rs=build(mid+1,rg);
    nd->update();
    return nd;
}

int query(Node *nd,int lf,int rg,int L,int R)
{
    if(L<=lf && rg<=R)
        return nd->gd;
    int mid=(lf+rg)>>1,rt=0,lt=0;
    if(L<=mid) lt=query(nd->ls,lf,mid,L,R);
    if(R>mid) rt=query(nd->rs,mid+1,rg,L,R);
    if(!rt) return lt;
    if(!lt) return rt;
    return gcd(lt,rt);
}

void insert(node *ne,node *&nd,int lf,int rg,int pos)
{
    nd=newnode();
    nd->sum=ne->sum+1;
    if(lf==rg) return ;
    int mid=(lf+rg)>>1;
    nd->ls=ne->ls,nd->rs=ne->rs;
    if(pos<=mid) insert(ne->ls,nd->ls,lf,mid,pos);
    else insert(ne->rs,nd->rs,mid+1,rg,pos);
}

int query(node *ne,node *nd,int lf,int rg,int K)
{
    if(lf==rg) return nd->sum-ne->sum;
    int mid=(lf+rg)>>1;
    if(K<=mid) return query(ne->ls,nd->ls,lf,mid,K);
    else return query(ne->rs,nd->rs,mid+1,rg,K);
}

int main()
{
    freopen("point.in","r",stdin);
    freopen("point.out","w",stdout);
    null=++tail;
    null->sum=0,null->ls=null->rs=null;
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
        scanf("%d",&a[i]);
    root1=build(1,n);
    root[0]=newnode();
    for(int i=1;i<=n;i++)
        root[i]=newnode();
    for(int i=1;i<=n;i++)
        insert(root[i-1],root[i],1,3000,a[i]);
    for(int i=n;i>=1;i--)
    {
        if(flag) break ;
        for(int j=1;j<=n-i+1;j++)
        {
            int L=j,R=j+i-1;
            int tmp=query(root1,1,n,L,R);
            if(query(root[L-1],root[R],1,3000,tmp))
                flag=1,ans++,bns=i-1,G.push_back(L);
        }
    }
    printf("%d %d\n",ans,bns);
    for(int i=0;i<G.size();i++)
        printf("%d ",G[i]);
    return 0;
}
/*
30
15 15 3 30 9 30 27 11 5 15 20 10 25 20 30 15 30 15 25 5 10 20 7 7 16 2 7 7 28 7 
*/

然後想了一下,好像可以直接列舉ak,然後往左右兩邊看最遠的區間在哪裡。這樣的複雜度是O(n^2)的,也是60分,雖然比我的優,但得分也沒什麼差別,然後就放棄了。放棄了。。放棄了。。。

考完以後,琢磨怎麼辦,想了一下決定先把暴力的優化寫出來,寫完了以後,測樣例,發現一個L可能被統計多次,寫了個unique然後CE。。忽然發現如果被更新過的點自己再更新絕對沒有之前優,可以跳過去,這樣就可以避免重複統計的情況

然後A了。。。

想了一下,這樣的時間複雜度主要集中在更新答案的地方,而只要這樣一改,每個點只會被更新兩次,時間複雜度O(2n)。。

完整程式碼:

#include<stdio.h>
#include<vector>
#include<algorithm>
using namespace std;

vector <int> G;

int n,a[1000010],maxn=0;

int main()
{
    freopen("point.in","r",stdin);
    freopen("point.out","w",stdout);
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
        scanf("%d",&a[i]);
    for(int i=1;i<=n;i++)
    {
        int tmp=0,L=i,R=i;
        for(int j=i-1;j>=1;j--)
        if(a[j]%a[i]==0) tmp++,L=j;
        else break ;
        for(int j=i+1;j<=n;j++)
        if(a[j]%a[i]==0) tmp++,R=j;
        else break ;
        if(tmp==maxn) G.push_back(L);
        else if(tmp>maxn)
        {
            G.clear();
            maxn=tmp;
            G.push_back(L);
        }
        i=R;
    }
    sort(G.begin(),G.end());
    int cnt=G.size();
    printf("%d %d\n",cnt,maxn);
    for(int i=0;i<cnt;i++)
        printf("%d ",G[i]);
    return 0;
}

嗯,就是這樣