NOIP2018划水记

正题

Day1

      前一天晚上说不着,看了半天发现枕头太高了。

      第二天早上一起来就洗了一个澡,神志清醒,跟好舍友Braz出去找早餐吃,结果找了半天麦当劳发现在门口????

      来到考场,准备好一切东西进入考场。

      第一题:积木大赛

      原题,就是有n个数,ai,每次可以操作一个区间,使得这个区间的数减一,并且这个区间必须大于0。问最少操作多少次? 

      明显是一个Dp,但是赛场上面我使用10000个vector来存每一个数的位置。然后根据每一次剩下的块数来加、减。

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<vector>
using namespace std;

bool tf[100010];
int n;
vector<int> f[10010];

int main(){
    scanf("%d",&n);
    int x;
    int mmax=0;
    for(int i=1;i<=n;i++){
        scanf("%d",&x);
        f[x].push_back(i);
        mmax=max(mmax,x);
    }
    tf[0]=tf[n+1]=true;
    int tot=1;
    int ans=0;
    for(int i=0;i<(int)f[0].size();i++){
        if(tf[f[0][i]-1] && tf[f[0][i]+1]) tot--;
        else if(!tf[f[0][i]-1] && !tf[f[0][i]+1]) tot++;
        tf[f[0][i]]=true;
    }
    for(int i=1;i<=mmax;i++){
        ans+=tot;
        for(int j=0;j<(int)f[i].size();j++){
            if(tf[f[i][j]-1] && tf[f[i][j]+1]) tot--;
            else if(!tf[f[i][j]-1] && !tf[f[i][j]+1]) tot++;
            tf[f[i][j]]=true;
        }
    }
    printf("%d",ans);
}

      第二题:给出n种面值的货币,要你求一种相同功能的货币并且是的面值尽量小。(相同功能指的是组成的价钱都能组成,不能组成的价钱都不能组成

      那么把面值排序,然后看一下当前面值是否能被前面的面值组成,如果可以,那么就舍弃。否则就要选,并且跑一次完全背包。就完事儿了。O(T*n*max_{a_i}+T*n\ log_2\ n).爆炸的几率不大。

   

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;

bool tf[25010];
int T,n;
int d[110];
int ans=0,mmax=0;

int main(){
    scanf("%d",&T);
    while(T--){
        scanf("%d",&n);
        memset(tf,false,sizeof(tf));
        mmax=0;
        for(int i=1;i<=n;i++) scanf("%d",&d[i]),mmax=max(mmax,d[i]);
        sort(d+1,d+1+n);
        tf[0]=true;ans=0;
        for(int i=1;i<=n;i++)
            if(!tf[d[i]]){
                ans++;
                for(int k=0;k+d[i]<=mmax;k++) tf[k+d[i]]|=tf[k];
            }
        printf("%d\n",ans);
    }
}

      第三题:n个点的树,找一组方案,选出m条路径,使得m条路径中的最短路径最长。

      这题我只会部分分,40到60.

      问了师兄,先用二分答案,然后验证,首先每个节点的儿子产生一条向上的路径,那么把这些路径长度加上节点到儿子的边排一遍序,大于等于mid的直接成为一条路径,其他的找到与其匹配的最小的加起来大于等于mid成为一条路径,剩下的选最大的往上送即可。具体的实现就是排序,然后用一个并查集维护后面第一个没有被选过的路径即可。

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;

int n,m;
struct edge{
    int y,next,c,a;
}s[100010];
int first[50010],len=0,f[50010];
bool tf[50010];
int op=0;
int d[50010];

void ins(int x,int y,int c){
    len++;
    s[len]=(edge){y,first[x],c};first[x]=len;
}

int find_upper(int l,int r,int x){
    int ans=0;
    while(l<=r){
        int mid=(l+r)/2;
        if(d[mid]>=x){
            r=mid-1;
            ans=mid;
        }
        else l=mid+1;
    }
    return ans;
}

int findpa(int x){
    if(f[x]!=x) return f[x]=findpa(f[x]);
    return x; 
}

int dfs(int x,int fa,int k){
    int tot=0,temp;
    for(int i=first[x];i!=0;i=s[i].next){
        int y=s[i].y;
        if(y==fa) continue;
        s[i].a=dfs(y,x,k)+s[i].c;
    }
    for(int i=first[x];i!=0;i=s[i].next){
        int y=s[i].y;
        if(y==fa) continue;
        if(s[i].a>=k) op++;
        else d[++tot]=s[i].a;
    }
    sort(d+1,d+1+tot);
    for(int i=1;i<=tot+1;i++) f[i]=i;
    int ans=0;
    for(int i=1;i<=tot;i++){
        if(findpa(i)!=i) continue;
        int x=find_upper(i+1,tot,k-d[i]);
        if(x==0) continue;
        int p=findpa(x);
        if(p==tot+1) continue;
        f[i]=f[i+1];
        f[p]=f[p+1];op++;
    }
    int mmax=0;
    for(int i=1;i<=tot;i++)
        if(findpa(i)==i) mmax=max(mmax,d[i]);
    return mmax;
}

bool check(int x){
    op=0;
    dfs(1,0,x);
    return op>=m;
}

int main(){
    scanf("%d %d",&n,&m);
    int x,y,c;
    int l=0,r=0;
    for(int i=1;i<=n-1;i++){
        scanf("%d %d %d",&x,&y,&c);
        ins(x,y,c);
        ins(y,x,c);
        r+=c;
    }
    int ans=0;
    while(l<=r){
        int mid=(l+r)/2;
        if(check(mid)) l=(ans=mid)+1;
        else r=mid-1;
    }
    printf("%d\n",ans);
}

      整一天看下来还是发挥得不怎么样,第三题其实挺好想的,平时应该多做点题来巩固。Day1期望250吧。

Day2

      这一天我和brz都晚起了,所以就很困惑,赶快去小卖部买了两条巧克力一条给brz,就匆匆进考场了。

      结果整套题看下来就自闭了。

       一眼看到第一题好像挺难的,看了看数据范围。 

      发现要不是个树,要么是个基环树。对于树,我们把一个点的儿子排一遍序,然后遍历即可。如果是基环树,先找到那个环上面的边,把它们记录下来,然后枚举这些边中那些边不能走,剩下的跟树的做法一样。

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<queue>
using namespace std;

int n,m;
struct edge{
    int y,next;
}s[10010];
int first[5010],len=1;
int ans[5010],isn=0,op[5010];
bool tf[5010];
int loop[5010],end;
bool fd,we;

void ins(int x,int y){
    len++;
    s[len]=(edge){y,first[x]};first[x]=len;
}

void dfs(int x,int fa,int *ans){
    ans[++ans[0]]=x;
    priority_queue<int , vector<int> , greater<int> > f;
    for(int i=first[x];i!=0;i=s[i].next){
        int y=s[i].y;
        if(y==fa || i==isn || i==(isn^1)) continue;
        f.push(y);
    }
    while(!f.empty()) {dfs(f.top(),x,ans);f.pop();}
}

void find_loop(int x,int fa){
    tf[x]=true;
    for(int i=first[x];i!=0;i=s[i].next){
        int y=s[i].y;
        if(y==fa) continue;
        if(tf[y]){
            fd=we=true;
            loop[++loop[0]]=i;
            end=y;
            break;
        }
        find_loop(y,x);
        if(fd){
            if(we) loop[++loop[0]]=i;
            if(x==end) we=false;
            break;
        }
    }
    tf[x]=false;
}

int main(){
    scanf("%d %d",&n,&m);
    int x,y;
    for(int i=1;i<=m;i++){
        scanf("%d %d",&x,&y);
        ins(x,y);
        ins(y,x);
    }
    if(m==n-1) dfs(1,0,ans);
    else{
        find_loop(1,0);	
        isn=loop[1];
        dfs(1,0,ans);
        for(int i=2;i<=loop[0];i++){
            isn=loop[i];
            op[0]=0;
            dfs(1,0,op);
            for(int j=1;j<=n;j++) 
                if(op[j]<ans[j]){
                    for(int k=j;k<=n;k++) ans[k]=op[k];
                    break;
                }
                else if(op[j]>ans[j]) break;
        }
    }
    for(int i=1;i<=n;i++) printf("%d ",ans[i]);
}

      第二题挺难的,在考场上只看出来了三条性质。

      1.对于每一条自左下到右上的斜线,肯定是11111000000,这样子的,否则就肯定存在一组路径不满足条件。

      2.对于10的交界处,它们右下的点到最后一个点所形成的矩阵是全0、全1.

      3.n等于1时,答案为2^n

      没有考虑n等于2的时候。就很亏,应该连20分都难拿到。

      其实跟正解接近了,下一步就是构造Dp。

      挺多人都是直接找规律,发现当n>=2,m>n时,答案是Ans_{n,n+1}*3^{m-n-1},所以打打表,优化一下。有关证明我也不是很会,瞪眼法吧。

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
using namespace std;

int d[9]={1,1,12,112,912,7136,56768,453504,3626752};
int d2[9]={1,1,36,336,2688,21312,170112,1360128,10879488};
int n,m;
long long ans=1;
long long mod=1e9+7;

long long ksm(long long x,long long t){
    long long tot=1;
    while(t){
        if(t%2) (tot*=x)%=mod;
        (x*=x)%=mod;
        t/=2;
    }
    return tot;
}

int main(){
    scanf("%d %d",&n,&m);
    if(n>m) swap(n,m);
    if(n==1) printf("%lld\n",ksm(2,m));
    else if(n==m) printf("%d\n",d[n]);
    else printf("%lld\n",d2[n]*ksm(3,m-n-1)%mod);
}

      第三题:最难的一题,用了两天才搞定。

      其实就是对于每一条边,两个端点至少有一个要被选,问最小代价。

      反过来说,就是不选一些点,两两之间要独立,求最大独立集

      首先,这是一个非常经典的树形Dp的问题。

      \\f[x][0]=\sum_{i\in son[x]} f[i][1] \\f[x][1]=\sum_{i\in son[x]} min(f[i][0],f[i][1])

      显然的。

      nm即可。当要求的两个点在一起的时候,并且都为0。那么就输出-1.具体实现用map。

      然后,要带修。其实选,就相当于把权值改成极小值,因为这样一定会选到那个点,不选就改成极大值,因为那样肯定不会选到它。

      最后再还原一下答案就可以了。

      就变成了动态Dp的裸题,把矩阵画出来即可。

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<map>
#define lc (now<<1)
#define rc ((now<<1)|1)
using namespace std;

int n,m;
long long w[100010];
struct edge{
    int y,next;
}s[200010];
long long mmax=(long long)1e14;
struct matrix{
    long long op[3][3];
    matrix operator*(const matrix b)const{
        matrix q;
        for(int i=1;i<=2;i++)
            for(int j=1;j<=2;j++){
                q.op[i][j]=mmax;
                for(int k=1;k<=2;k++)
                    q.op[i][j]=min(q.op[i][j],op[i][k]+b.op[k][j]);
            }
        return q;
    }
}tr[300010];
map<pair<int , int> , int> mp;
int first[100010],len=0;
int tot[100010],son[100010];
int image[100010],fact[100010],last[100010],fa[100010],top[100010];
long long f[100010][2],g[100010][2];
char ch[3];

void ins(int x,int y){
    len++;
    s[len]=(edge){y,first[x]};first[x]=len;
}

void dfs_1(int x){
    tot[x]=1;
    for(int i=first[x];i!=0;i=s[i].next){
        int y=s[i].y;
        if(y==fa[x]) continue;
        fa[y]=x;
        dfs_1(y);
        tot[x]+=tot[y];
        if(tot[y]>tot[son[x]]) son[x]=y;
    }
}

void dfs_2(int x,int tp){
    image[x]=++len;fact[len]=x;top[x]=tp;
    if(son[x]!=0) dfs_2(son[x],tp);
    else {last[tp]=x;return ;}
    for(int i=first[x];i!=0;i=s[i].next){
        if(s[i].y==fa[x] || s[i].y==son[x]) continue;
        dfs_2(s[i].y,s[i].y);
    }
}

void tr_dp(int x){
    f[x][1]=g[x][1]=w[x];
    f[x][0]=g[x][0]=0;
    for(int i=first[x];i!=0;i=s[i].next){
        int y=s[i].y;
        if(y==fa[x]) continue;
        tr_dp(y);
        f[x][1]+=min(f[y][0],f[y][1]);
        f[x][0]+=f[y][1];
        if(y==son[x]) continue;
        g[x][1]+=min(f[y][0],f[y][1]);
        g[x][0]+=f[y][1];
    }
}

void update(int now,int l,int r,int x){
    if(l==r){
        tr[now].op[1][1]=mmax;tr[now].op[1][2]=g[fact[x]][0];
        tr[now].op[2][1]=tr[now].op[2][2]=g[fact[x]][1];
        return ;
    }
    int mid=(l+r)/2;
    if(x<=mid) update(lc,l,mid,x);
    else update(rc,mid+1,r,x);
    tr[now]=tr[lc]*tr[rc];
}

matrix query(int now,int x,int y,int l,int r){
    if(x==l && y==r) return tr[now];
    int mid=(l+r)/2;
    if(y<=mid) return query(lc,x,y,l,mid);
    else if(mid<x) return query(rc,x,y,mid+1,r);
    else return query(lc,x,mid,l,mid)*query(rc,mid+1,y,mid+1,r);
}

void build(int now,int l,int r){
    if(l==r){
        tr[now].op[1][1]=mmax;tr[now].op[1][2]=g[fact[l]][0];
        tr[now].op[2][1]=tr[now].op[2][2]=g[fact[l]][1];
        return ;
    }
    int mid=(l+r)/2;
    build(lc,l,mid);build(rc,mid+1,r);
    tr[now]=tr[lc]*tr[rc];
}

void change(int x,long long y){
    g[x][1]+=y-w[x];
    w[x]=y;
    matrix A,B;
    int st,ed;
    while(1){
        st=top[x];ed=last[top[x]];
        A=query(1,image[st],image[ed],1,n);
        update(1,1,n,image[x]);
        B=query(1,image[st],image[ed],1,n);
        x=fa[st];
        if(x==0) break;
        g[x][0]+=B.op[2][2]-A.op[2][2];
        g[x][1]+=min(B.op[1][2],B.op[2][2])-min(A.op[1][2],A.op[2][2]);
    }
}

int main(){
    scanf("%d %d %s",&n,&m,ch);
    for(int i=1;i<=n;i++) scanf("%lld",&w[i]);
    int x,y;
    for(int i=1;i<=n-1;i++) {
        scanf("%d %d",&x,&y);
        mp[make_pair(x,y)]=1;
        ins(x,y);ins(y,x);
    }
    len=0;
    dfs_1(1);dfs_2(1,1);
    tr_dp(1);
    build(1,1,n);
    int a,b;
    int temp;
	long long xx,yy,to;
    matrix A;
    for(int i=1;i<=m;i++){
        scanf("%d %d %d %d",&a,&x,&b,&y);
    	temp=0,to=0;
        if(x==0 && y==0 && (mp[make_pair(a,b)] || mp[make_pair(b,a)])){
        	printf("-1\n");
        	continue;
        }
        xx=(x==0?mmax:(to++,temp+=w[a],-mmax));
        yy=(y==0?mmax:(to++,temp+=w[b],-mmax));
		x=w[a],y=w[b];
    	change(a,xx);
    	change(b,yy);
    	A=query(1,image[1],image[last[1]],1,n);
    	long long ans=min(A.op[1][2],A.op[2][2])+to*mmax+temp;
    	printf("%lld\n",ans);
        change(a,x);
        change(b,y);
    }
}

      我才不会告诉你我开了O2

      期望加起来大概在100+100+50+100+20+64=434,但是我不知道NOIp这种非人类的数据会不会把我卡成爆零。

      总的来说,还是不能心浮气躁,面对问题要仔细思考。在考场上不能做得比平时好很多,所以才要把平时做得更好啊,不断的比赛,不断地刷题,在每一次刷题后不断地总结,这才是成长啊!要是整日颓废,不过专心,还不如滚回去搞文化课?

       在接下来的一段时间内,可能要做的就是两件事情吧:

       1.巩固旧算法,学习新算法(FFT,NTT,莫比乌斯反演,平面几何虽然学了,但是总是不知道有什么应用,而且学得不精,需要刷题来与其他算法更好的结合吧)。

       2.刷题:在GDKOI2018之前,在bzoj,借助别人的题表,刷够300题,题表会在本博客中贴出。平均每天3题,我一定行!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值