历年NOIP题

做了几天远古老题,发现不可做,于是咕掉。。转而从2006开始。。

1997:

P1549 棋盘问题(2):搜索,优化搜索顺序,对于第一行第一列先搜小的(但是其实这样是错的,仅仅能过原题)
加强版咕。

1998:

P1011 车站:类似斐波那契,推式子即可。
P1012 拼数:sort cmp:a+b>b+a
P1013 进制位:观察性质发现,一定是n-1进制。
原因:若进制 <n-1 会出现重复字母,若进制 >n-1 则不会进位导致出现更大的数。
判断一个数字是什么:
其实就是这样

1999:

P1016 旅行家的预算
P1021 邮票面值设计:直接搜索方案,最后做一个基于值域的背包

2000:

P1004 方格取数:怎么跟传纸条一样啊,,,还是\(f[i][j][k][l]\) 表示一个人是\(i,j\),另一个人是\(k,l\),
\(f[i][j][k][l]\\=\max(f[i-1][j][k-1][l],f[i-1][j][k][l-1],f[i][j-1][k][l-1],f[i][j-1][k-1][l])\\+a[i][j]+a[k][l]-(i==k\&\&k==l)?a[i][j]:0\)
P1017 进制转换:通常进制转换是先\(%M\)然后\(/M\),但是这道题 \(%M\) 后可能会有负数(负数%负数),所以我们给每个余数强制+(-m)即可。

//远古代码
void turn(int n,int m)
{
    if(n==0) return;
    else
    {
        if(n>0||n%m==0)
        {
            turn(n/m,m);
            printf("%c",c[n%m]);
            return;
        }
        else
        {
            turn(n/m+1,m);
            printf("%c",c[-m+n%m]);
            return;
        }
    }
}

P1018 乘积最大:辣鸡DP+高精(摸)
P1019 单词接龙:

2006:

P1063 能量项链:拆环为链+区间DP。
P1064 金明的预算方案:假设我们已经背了前k-1组(数组f),对于第k组,先把主件背到包含前k-1组的背包里(另开一个数组h),然后对所有附件在h数组上跑01背包,然后f=max(f,h),表示不取或取第i组。
P1065 作业调度方案:ri读题读错+数组开小、、、就是个模拟(我还以为是DP)
P1066 2^k进制数:高精+组合数。若\(k|w\),则方案数\(\sum_{i=2}^{\frac{w}{k}}C(2^k-1,i),i\)代表\(1\)\(i\)位不为\(0\);若\(k\nmid w\),若高位填\(0\),有\(\sum_{i=2}^{\lfloor\frac{w}{k}\rfloor}C(2^k-1,i)\),否则我们要枚举高位填什么,即\(\sum_{i=1}^{2^{w\%k}-1}C(2^k-1-i,\lfloor\frac{w}{k}\rfloor)\)

2007

P1005 矩阵取数游戏:就是个区间DP,\(f[i][j]=\max(f[i-1][j]+a[i-1]*2^{m-(j-i)-1},f[i][j+1]+a[j+1]*2^{m-(j-i)-1})\),但是又要高精。

P1097 统计数字:sort一遍结束

P1098 字符串的展开:模拟。拿中间(i)的去判两边的(i-1,i+1)。

P1099 树网的核:\(\mathcal{O}(n^3)\):直接找直径枚举两个端点,然后整个dfs一遍

\(\mathcal{O}(n^2)\):发现选的部分一定是尽量长,于是确定了左端点,就可以确定右端点,然后dfs

\(\mathcal{O}(nlog(\sum w_i))\):二分在选择的上的两端点到直径两端点的距离,然后通过dfs,把这段选择区间的点的最长链找出来,判定。

\(\mathcal{O}(n)\):观察到我们其实可以先求出来直径上每个点不过直径的最长链,然后在直径上跑一个类似滑动窗口的东西。(更优秀的见lyd的书上)。

2008

P1006 传纸条:同方格取数,可以四维,三维,二维DP or 网络流。

P1125 笨小猴:模拟。

P1149 火柴棒等式:预处理出每个数(最大最大也不可能超过1111(8根))要用多少火柴棒,然后枚举 \(A\)\(B\)

P1155 双栈排序:刚开始写的贪心。。。然后弃掉了。

我们首先观察到,若有\(i<j<k\ \&\&\ a[k]<a[i]<a[j]\),那么 \(i,j\) 一定不在一个栈里面。

现在有两个栈,我们可以将不在一个栈中的点连边,然后判一下是否是一个二分图即可确定是否能双栈排序。

排序时注意:能先弹第一个栈的话,一定要先弹掉(字典序更小)。

也可以看看zjp_shadow的题解。

#include<bits/stdc++.h>
#define R register int
using namespace std;
namespace Luitaryi {
inline int g() { R x=0,f=1;
    register char ch; while(!isdigit(ch=getchar())) f=ch=='-'?-1:f;
    do x=x*10+(ch^48); while(isdigit(ch=getchar())); return x*f;
} const int N=1010,Inf=0x7f7f7f7f;
#define pc(x) putchar(x) 
int n,a[N],mn[N],co[N];
int stk[2][N],top[2],pos=1;
vector <int> e[N];
inline bool pop(int whi) {
    if(top[whi]&&stk[whi][top[whi]]==pos) {
        pc(whi?'d':'b'),pc(' '),--top[whi],++pos;
        return true;
    }
    return false;
}
inline void push(int x,int whi) {
    if(whi==1) while(pop(0));//先能弹第一个就弹 
    while(top[whi]&&stk[whi][top[whi]]<x)
        if(!pop(whi)) pop(whi^1);//弹自己的同时弹另一个 
    if(whi==1) while(pop(0));//还是能弹接着弹 
    stk[whi][++top[whi]]=x; pc(whi?'c':'a'),pc(' ');
}
inline void main() {
    n=g(); for(R i=1;i<=n;++i) a[i]=g();
    mn[n+1]=n+1;
    for(R i=n;i;--i) mn[i]=min(mn[i+1],a[i]);
    for(R i=1;i<=n;++i) for(R j=i+1;j<=n;++j) if(mn[j+1]<a[i]&&a[i]<a[j]) 
        e[i].push_back(j),e[j].push_back(i),co[i]=co[j]=-1; 
    for(R i=1;i<=n;++i) if(!~co[i]) {
        queue<int> q; q.push(i); co[i]=0;
        while(q.size()) { R u=q.front(); q.pop();
            for(R i=0,lim=e[u].size();i<lim;++i) { R v=e[u][i];
                if(~co[v]&&co[u]!=(co[v]^1)) return (void) puts("0");
                if(!~co[v]) q.push(v);
                co[v]=co[u]^1;
            }
        }
    }
    for(R i=1;i<=n;++i) push(a[i],co[i]);
    register bool flg=true; while(flg) {
        flg=false; while(pop(0)) flg=true; while(pop(1)) flg=true;
    }
}
} signed main() {Luitaryi::main(); return 0;}

2009

P1071 潜伏者:双射,开一个数组模拟即可。

P1072 Hankson 的趣味题:可以直接暴力枚举约数(试除),也可用搜索优化这个过程(枚举每个质因数选了几个)(记住\(10^9\) 以内的约数最多的数约数只有 \(1536\) );也有更优秀的做法,见lyd的书。

P1073 最优贸易:跑一边 \((1,u)\) 最长路,跑一边 \((u,n)\) 最短路.

P1074 靶形数独:搜索,当然要按一定顺序搜,我们先把每一行没有出现过的点的数量计算出来,然后排序行,之后对每行的点编号,爆搜即可。。。。

2010

P1540 机器翻译:直接按题意模拟。

P1541 乌龟棋:四维DP,存每种牌的张数。

P1525 关押罪犯:带权 or 种类并查集,把尽量大的边作为矛盾边,只要发现两个点属于同一种颜色 or 同一集合,break;
还可以二分边权然后染色,判断是否是二分图。

P1514 引水入城:记忆化搜索,也可以不记忆化,复杂度也对,我们发现,不同的点覆盖的区间一定是不交的,即不会出现下面的情况:

所以我们可以求出第一行每个点覆盖的区间,然后原题就转化为区间覆盖问题。这个问题可以贪心(md怎么又是贪心),详见代码。

bool vis[N][N];
int n,m,cnt,tot,l[N][N],r[N][N],f[N],h[N][N];
struct node { int l,r; 
    inline bool operator < (const node& that) const
        {return l<that.l;}
}s[N];
inline void dfs(int x,int y) {
    vis[x][y]=true;
    for(R i=0;i<4;++i) { R xx=x+dx[i],yy=y+dy[i];
        if(xx<1||xx>n||yy<1||yy>m||h[xx][yy]>=h[x][y]) continue;
        if(!vis[xx][yy]) dfs(xx,yy);
        l[x][y]=min(l[xx][yy],l[x][y]);//求出每个点能管辖的最左
        r[x][y]=max(r[xx][yy],r[x][y]);//和最右
    }
}
inline void main() {
    n=g(),m=g(); for(R i=1;i<=n;++i) for(R j=1;j<=m;++j) h[i][j]=g();
    memset(l,0x3f,sizeof l); for(R i=1;i<=m;++i) l[n][i]=r[n][i]=i;
    for(R i=1;i<=m;++i) if(!vis[1][i]) dfs(1,i);
    for(R i=1;i<=m;++i) if(!vis[n][i]) ++cnt;
    if(cnt) return puts("0"),printf("%d\n",cnt),void(); puts("1");
    for(R i=1;i<=m;++i) s[i].l=l[1][i],s[i].r=r[1][i];//,cout<<s[i].l<<' '<<s[i].r<<endl;
    sort(s+1,s+m+1); R p=1,LL=1,RR=0; 
    memset(f,0x3f,sizeof f); f[0]=0; 
    while(LL<=m) {
        while(s[p].l<=LL&&p<=m) RR=max(RR,s[p].r),++p; //在包含的区间中,尽量往右跳
        ++tot,LL=RR+1;
    } printf("%d\n",tot);
}

2011

P1003 铺地毯:模拟,然而正序倒序都行。

P1311 选择客栈:计算以每个点为右端点的方案数,详见代码。

int n,k,P; ll ans;
int c[N],s[101];
inline void main() { n=g(),k=g(),P=g();
    for(R i=1,p,lst=0;i<=n;++i) {
        c[i]=g(),p=g(); if(p<=P) {//把上次小于P的位置到当前位置的点加进桶里
            for(R j=lst+1;j<=i;++j) ++s[c[j]];
            lst=i,ans+=s[c[i]]-1;
        } else ans+=s[c[i]];
    }
    printf("%lld\n",ans);
}

P1312 Mayan游戏:大模拟。。。

一个贪心:只有左边没有块时才考虑左移,否则我们不如右移。

const int N=8;
const int dx[]={-1,1,0,0},dy[]={0,0,-1,1},H=5,L=7;
int n,ansx[N],ansy[N],ansd[N];
int s[N][N][N]; bool vis[N][N];
inline bool ck(int x,int y) {return x<1||x>5||y<1||y>7;}
inline void ins(int d,int x,int y,int dir) {ansx[d]=x,ansy[d]=y,ansd[d]=dir;}
inline void drop(int d) {
    for(R i=0;i<H;++i) { R sz=-1;
        for(R j=0;j<L;++j) if(s[d][i][j])
            s[d][i][++sz]=s[d][i][j];
        while(++sz<L) s[d][i][sz]=0;
    }
}
inline void del(int d) { register bool flg=true;
    while(flg) { drop(d);//让块掉落
        flg=false; for(R i=0;i<H;++i) for(R j=0;j<L;++j) if(s[d][i][j]) {
            if(i<3) if(s[d][i][j]==s[d][i+1][j]&&s[d][i][j]==s[d][i+2][j]) //判横向的3个
                flg=vis[i][j]=vis[i+1][j]=vis[i+2][j]=true;
            if(j<5) if(s[d][i][j]==s[d][i][j+1]&&s[d][i][j]==s[d][i][j+2]) //判纵向的3个
                flg=vis[i][j]=vis[i][j+1]=vis[i][j+2]=true;
        } for(R i=0;i<H;++i) for(R j=0;j<L;++j) 
            if(vis[i][j]) s[d][i][j]=vis[i][j]=0;//清除
    } 
}
inline bool dfs(int d) {
    memcpy(s[d],s[d-1],sizeof s[d-1]); del(d); if(d==n+1) {
        for(R i=0;i<H;++i) if(s[d][i][0]) return false;//检查当前局面
        return true;
    } for(R i=0;i<H;++i) for(R j=0;j<L;++j) if(s[d][i][j]) {
        if(i<4&&s[d][i][j]!=s[d][i+1][j]) {//向右交换(若相同颜色则不交换的剪枝可能是假的)
            ins(d,i,j,1),swap(s[d][i][j],s[d][i+1][j]);
            if(dfs(d+1)) return true; swap(s[d][i][j],s[d][i+1][j]);
        } if(i&&!s[d][i-1][j]) {//向左移动
            ins(d,i,j,-1),swap(s[d][i][j],s[d][i-1][j]);
            if(dfs(d+1)) return true; swap(s[d][i][j],s[d][i-1][j]);
        }
    } return false;
}
inline void main() {
    n=g(); for(R i=0;i<H;++i) for(R j=0,x=g();x;++j,x=g()) s[0][i][j]=x;
    if(dfs(1)) for(R i=1;i<=n;++i) printf("%d %d %d\n",ansx[i],ansy[i],ansd[i]);
    else puts("-1");
}

P1313 计算系数:二项式定理。

P1314 聪明的质监员:二分 \(W\) 的值。然后每次 \(\mathcal{O}(n)\) 重新计算前缀和,然后 \(\mathcal{O}(m)\) 计算代价。

int n,m,mn=1e9,mx=0,w[N],v[N],l[N],r[N];
ll S,s[N],sum[N],ans=1e17;
inline bool ck(int x) { register ll ret=0;
    memset(s,0,sizeof s),memset(sum,0,sizeof sum);
    for(R i=1;i<=n;++i) if(w[i]>=x) s[i]=s[i-1]+1,sum[i]=sum[i-1]+v[i];
    else s[i]=s[i-1],sum[i]=sum[i-1];
    for(R i=1;i<=m;++i) ret+=(s[r[i]]-s[l[i]-1])*(sum[r[i]]-sum[l[i]-1]);
    ans=min(ans,llabs(ret-S)); return ret>S;
}
inline void main() {
    n=g(),m=g(),S=g(); for(R i=1;i<=n;++i) 
        w[i]=g(),v[i]=g(),mn=min(mn,w[i]),mx=max(mx,w[i]);
    for(R i=1;i<=m;++i) l[i]=g(),r[i]=g(); R LL=0,RR=mx+1;
    while(LL<RR) { R md=LL+RR>>1;
        if(ck(md)) LL=md+1; else RR=md;
    } printf("%lld\n",ans);
}

P1315 观光公交:好题,\(\mathcal{O}(kn)\) 的贪心(自己简直zz)
对于每个加速器,我们只用找到他能贡献的最大人数即可,每次重复这个过程。
显然,当一个景点最晚到的人比 从1号点到这个点的时间还要短时,我们就不用考虑后面的点了。

struct node {int a,b,t;}s[M];
int n,m,k,low[N],dst[N],t[N],c[N],ans;
//dst[i]从1到i用时,low[i]到点i最晚的游客
inline void main() {
    n=g(),m=g(),k=g(); 
    for(R i=1;i<n;++i) t[i]=g();
    for(R i=1;i<=m;++i) {
        s[i].t=g(),s[i].a=g(),s[i].b=g();
        low[s[i].a]=max(low[s[i].a],s[i].t);
        ++c[s[i].b];
    } R lst=0;
    for(R i=1;i<=n;++i) dst[i]=lst,lst=max(dst[i],low[i]),lst+=t[i];
    while(k--) {
        R mx=0,tmp=0,pos=0;
        for(R i=2,j;i<=n;i=j+1) { tmp=0;
            while(!t[i-1]) ++i;
            for(j=i;j<=n;++j) {
                tmp+=c[j];
                if(dst[j]<=low[j]) break;   
            }
            if(tmp>mx) mx=tmp,pos=i;
        } --t[pos-1];//在路上用加速器
        for(R i=pos;i<=n;++i) {
            --dst[i]; if(dst[i]<low[i]) break;
        }
    } for(R i=1;i<=m;++i) ans+=dst[s[i].b]-s[i].t;
    printf("%d\n",ans);
}

2012

P1079 Vigenère 密码:模拟;

P1080 国王游戏:贪心,邻项交换;

P1081 开车旅行:倍增DP,先排序,然后用链表串起来这样,预处理出最小点和次小点;

const int N=100010,L=16,L2=2;
int n,m,h[N],pos[N],to1[N],to2[N],ans;
ll f[L+1][N][2],d1[L+1][N][2],d2[L+1][N][2],A,B,ansA,ansB;
struct node { int h,id,l,r;
    inline bool operator < (const node& that) const {return h<that.h;}
}a[N];
inline bool ck(int l,int r,int md) {
    if(!l) return 0; if(!r) return 1;
    return a[md].h-a[l].h<=a[r].h-a[md].h;
}
inline void solve(int i,ll d) {
    A=B=0; register bool k=0;
    for(R t=L;~t;--t) if(f[t][i][k]&&A+B+d1[t][i][k]+d2[t][i][k]<=d) {
        A+=d1[t][i][k],B+=d2[t][i][k]; if(i==0) k^=1; i=f[t][i][k];
    }
}
inline void main() {
    n=g(); for(R i=1;i<=n;++i) h[i]=a[i].h=g(),a[i].id=i;
    sort(a+1,a+n+1); for(R i=1;i<=n;++i) pos[a[i].id]=i,a[i].l=i-1,a[i].r=i+1; a[n].r=0;
    for(R i=1;i<=n;++i) { R l=a[pos[i]].l,r=a[pos[i]].r;
        if(ck(l,r,pos[i])) { to2[i]=a[l].id; //cout<<
            if(ck(a[l].l,r,pos[i])) to1[i]=a[a[l].l].id; else to1[i]=a[r].id;
        } else { to2[i]=a[r].id;
            if(ck(l,a[r].r,pos[i])) to1[i]=a[l].id; else to1[i]=a[a[r].r].id;
        } if(l) a[l].r=r; if(r) a[r].l=l;
    } 
    for(R i=1;i<=n;++i) {
        if(to1[i]) f[0][i][0]=to1[i],d1[0][i][0]=abs(h[i]-h[to1[i]]);
        if(to2[i]) f[0][i][1]=to2[i],d2[0][i][1]=abs(h[i]-h[to2[i]]);
        d1[0][i][1]=d2[0][i][0]=0;
    } for(R t=1;t<=L;++t) for(R i=1;i<=n;++i) for(R j=0,k;j<L2;++j) {
        if(t==1) k=j^1; else k=j;
        if(f[t-1][i][j]) 
            f[t][i][j]=f[t-1][f[t-1][i][j]][k],
            d1[t][i][j]=d1[t-1][i][j]+d1[t-1][f[t-1][i][j]][k],
            d2[t][i][j]=d2[t-1][i][j]+d2[t-1][f[t-1][i][j]][k];
    } R x0=g(); ansA=1;
    for(R i=1;i<=n;++i) {
        solve(i,x0); if(!B) A=1; 
        if(A*ansB<ansA*B||(A*ansB==ansA*B&&h[i]>h[ans])) ansA=A,ansB=B,ans=i;
    } printf("%d\n",ans); m=g();
    while(m--) {R p=g(),d=g(); solve(p,d); printf("%lld %lld\n",A,B);}
}

P1082 同余方程:exgcd;

P1083 借教室:对于每个操作,我们差分标记,检查时用前缀和,若发现不合法倒着撤销每一次操作,直到合法。
P1084 疫情控制:二分+倍增+贪心。

int n,m,cnt;
struct node {int u; ll res; node() {}
    node(int _u,ll _res) {u=_u,res=_res;}
    inline bool operator < (const node& that) const {return res<that.res;}
}mem[N];
int vr[N<<1],nxt[N<<1],fir[N],w[N<<1],fa[N][B+1],d[N],t[N],mem1[N],mem2[N];
bool flg[N],cal[N]; 
ll dis[N][B+1],mx;
inline void add(int u,int v,int ww) {
    vr[++cnt]=v,nxt[cnt]=fir[u],fir[u]=cnt,w[cnt]=ww;
    vr[++cnt]=u,nxt[cnt]=fir[v],fir[v]=cnt,w[cnt]=ww;
}
inline void dfs(int u) {
    for(R i=fir[u];i;i=nxt[i]) { R v=vr[i];
        if(d[v]) continue; dis[v][0]=w[i],fa[v][0]=u,d[v]=d[u]+1; dfs(v);
    }
}
inline bool calc(int u,int fa) { register bool son=true;
    if(flg[u]) return false;
    for(R i=fir[u];i;i=nxt[i]) { R v=vr[i];
        if(v==fa) continue; son=false; if(calc(v,u)) return true;
    } return son;
}
inline bool ck(int lim) { R cnt=0,t1=0,t2=0; //cout<<lim<<endl;
    memset(cal,0,sizeof cal),memset(mem,0,sizeof mem),memset(flg,0,sizeof flg);
    memset(mem1,0,sizeof mem1),memset(mem2,0,sizeof mem2);
    for(R i=1;i<=m;++i) { R u=t[i]; register ll len=0;
        for(R t=B;~t;--t) if(fa[u][t]>1&&len+dis[u][t]<=lim)//向上爬 
            len+=dis[u][t],u=fa[u][t];
        if(fa[u][0]==1&&len+dis[u][0]<=lim) mem[++cnt]=node(u,lim-len-dis[u][0]);//能爬到根
        else flg[u]=true;//定住 
    }
    for(R i=fir[1];i;i=nxt[i]) { R v=vr[i];
        cal[v]=calc(v,1);//子树中有没被覆盖的叶子 
    } sort(mem+1,mem+cnt+1);//
    for(R i=1;i<=cnt;++i) { 
        if(cal[mem[i].u]&&mem[i].res<dis[mem[i].u][0]) cal[mem[i].u]=0;//跑不到一个来回还是就定住 (注意之前已经减过一个dis[u][0]了) 
        else mem1[++t1]=mem[i].res;//剩下的 
    }
    for(R i=fir[1];i;i=nxt[i]) { R v=vr[i];
        if(cal[v]) mem2[++t2]=dis[v][0];//未覆盖的 
    } 
    if(t1<t2) return false;
    sort(mem1+1,mem1+t1+1),sort(mem2+1,mem2+t2+1);//便于下面的双指针扫描 
    R p=1,q=1; while(p<=t1&&q<=t2) 
        if(mem1[p]>=mem2[q]) ++p,++q; else ++p;
    return q>t2;
}
inline void main() {
    n=g(); for(R i=1,u,v,w;i<n;++i) u=g(),v=g(),w=g(),add(u,v,w),mx+=w;
    d[1]=1;dfs(1); for(R t=1;t<=B;++t) for(R i=1;i<=n;++i) 
        fa[i][t]=fa[fa[i][t-1]][t-1],dis[i][t]=dis[i][t-1]+dis[fa[i][t-1]][t-1];
    m=g(); for(R i=1;i<=m;++i) t[i]=g(); R l=0,r=mx; while(l<r) {
        R md=l+r>>1; if(ck(md)) r=md; else l=md+1;
    } if(r==mx) puts("-1");
    else printf("%d\n",l);
}

2013

P1965 转圈游戏:快速幂+%

模模模

P1966 火柴排队:日,排序。
我们发现,直接双关键字排序求出每个数的排名(感觉只按值排序不一定对,不过你要是稳定排序就OK了),然后显然交换B与交换A是一样的,我们只要交换其中一个就可以,这样我们把B中每个数对应的A中的数原来的位置记录下来即可,然后把B按原来的位置sort回去,求逆序对。

int n,ans,c[N];
inline void add(int x) {for(;x<=n;x+=x&-x) ++c[x];}
inline int query(int x) { R ret=0;
    for(;x;x-=x&-x) ret+=c[x]; return ret;
}
struct node { ll v; int p,id;
    inline bool operator < (const node& that) const 
        {return p<that.p;}
}a[N],b[N];
inline bool cmp(const node& _this,const node& _that) 
    {return _this.v<_that.v||(_this.v==_that.v&&_this.p<_that.p);}
inline void main() {
    n=g(); for(R i=1;i<=n;++i) a[i].v=g(),a[i].p=i;
    for(R i=1;i<=n;++i) b[i].v=g(),b[i].p=i;
    sort(a+1,a+n+1,cmp),sort(b+1,b+n+1,cmp);
    for(R i=1;i<=n;++i) b[i].id=a[i].p;
    sort(b+1,b+n+1); for(R i=n;i;--i) 
        ans=(ans+query(b[i].id))%M,add(b[i].id);
    printf("%d\n",ans);
}

P1967 货车运输:最大生成树+倍增lca && 链上最小值。

P1969 积木大赛:贪心(md为什么又是贪心),见代码。

signed main() {
    int n,a,last=0,ans=0;
    cin>>n;
    for(int i=1;i<=n;i++) {
        cin>>a;
        if(a>last)ans+=(a-last);
        last=a;
    }
    cout<<ans<<endl;
}

P1970 花匠:类似DP,\(f\) 表示最后是下降的的最长长度,\(h\) 表示是最后上升的最长长度。
注意一个贪心,我们不会从更靠前的地方继承。大致看起来最后答案是连续的。

inline void main() {
    R n=g(); R f=1,h=1,lst=g();
    for(R i=2,x;i<=n;++i) { x=g();
        if(x>lst) f=max(f,h+1);
        if(x<lst) h=max(h,f+1); 
        lst=x;
    } printf("%d\n",max(f,h));
}

P1979 华容道:注意到我们不能每次都bfs。注意到只有空格子和我们要动的块是相邻的 才是有用状态。注意到各状态之间可以连边。于是我们可以每次跑出初始状态到最终状态的最短路。
复杂度 \(\mathcal{O}(m+q*km),m\leq 16n^2\)
好像边的数组开小了。

const int dx[]={1,-1,0,0},dy[]={0,0,1,-1},N=40,Inf=0x3f3f3f3f,L=4;
int n,m,q,num,cnt; bool a[N][N],flg[N][N],vis[N*N*4];
int id[N][N][L],d[N*N*4]; 
int vr[N*N*4<<1],nxt[N*N*4<<1],fir[N*N*4<<1],w[N*N*4<<1];
struct node {int x,y,w;node() {} node(int _x,int _y,int _w) {x=_x,y=_y,w=_w;}};
inline void add(int u,int v,int ww) {vr[++cnt]=v,nxt[cnt]=fir[u],fir[u]=cnt,w[cnt]=ww;}
inline int dis(int x1,int y1,int x2,int y2,int x3,int y3) {//x1,y1起点,x2,y2终点,x3,y3不能经过的点
    if(x1==x2&&y1==y2) return 0;
    memset(flg,0,sizeof flg);
    queue<node> q; q.push(node(x1,y1,0)),flg[x1][y1]=true;
    while(q.size()) { register node u=q.front(); q.pop();
        if(u.x==x2&&u.y==y2) return u.w;
        for(R i=0;i<4;++i) { R x=u.x+dx[i],y=u.y+dy[i];
            if(x<1||x>n||y<1||y>m) continue;
            if(flg[x][y]||!a[x][y]) continue;
            if(x==x3&&y==y3) continue;
            q.push(node(x,y,u.w+1)); flg[x][y]=true;
        }
    } return Inf;
}
inline int spfa(int x1,int y1,int x2,int y2,int x3,int y3) {//x1,y1空格子,x2,y2起点,x3,y3终点
    queue<int> q; if(x2==x3&&y2==y3) return 0;
    memset(d,0x3f,sizeof d); R ret=Inf;
    for(R k=0;k<L;++k) if(id[x2][y2][k]) {
        d[id[x2][y2][k]]=dis(x1,y1,x2+dx[k],y2+dy[k],x2,y2);
        q.push(id[x2][y2][k]),vis[id[x2][y2][k]]=1;
    } while(q.size()) { R u=q.front(); q.pop(); vis[u]=false;
        for(R i=fir[u];i;i=nxt[i]) { R v=vr[i];
            if(d[v]>d[u]+w[i]) {
                d[v]=d[u]+w[i]; if(!vis[v]) vis[v]=true,q.push(v);
            }
        }
    } for(R k=0;k<L;++k) if(id[x3][y3][k]) ret=min(ret,d[id[x3][y3][k]]);
    return ret==Inf?-1:ret;
}
inline void main() {
    n=g(),m=g(),q=g(); for(R i=1;i<=n;++i) for(R j=1;j<=m;++j) a[i][j]=g();
    for(R i=1;i<=n;++i) for(R j=1;j<=m;++j) for(R k=0;k<L;++k) 
        if(a[i][j]&&a[i+dx[k]][j+dy[k]]) id[i][j][k]=++num;
    for(R i=1;i<=n;++i) for(R j=1;j<=m;++j) for(R k=0;k<L;++k) 
        if(id[i][j][k]) add(id[i][j][k],id[i+dx[k]][j+dy[k]][k^1],1);//交换空白块和正常块 
    for(R i=1;i<=n;++i) for(R j=1;j<=m;++j) for(R k=0;k<L;++k) for(R l=0;l<L;++l)
        if(k!=l&&id[i][j][k]&&id[i][j][l]) 
            add(id[i][j][k],id[i][j][l],dis(i+dx[k],j+dy[k],i+dx[l],j+dy[l],i,j));//同块不同白块转移 
    for(R i=1;i<=q;++i) {
        R ex=g(),ey=g(),sx=g(),sy=g(),tx=g(),ty=g();
        printf("%d\n",spfa(ex,ey,sx,sy,tx,ty));
    }
}

2014

P1328 生活大爆炸版石头剪刀布:直接模拟。

P1351 联合权值:\(ans=\sum_{u}\sum_{x,(u,x)}\sum_{y,(u,y),y!=x} w_x\times w_y\)
所以每条边只会被枚举两次,所以复杂度\(\mathcal{O}(n)\)

P1941 飞扬的小鸟:完全背包。

P2038 无线网络发射器选址:枚举,模拟。

P2296 寻找道路:bfs 反图一遍,找到终点能到的点集,然后处理出每个点的所有出点是否都是能到终点,然后在合法的点上再bfs一遍。

int n,m,s,t;
bool flg[N];
struct G {
int cnt,vr[M],nxt[M],fir[N],d[N];
queue<int> q;
inline void add(int u,int v) 
    {vr[++cnt]=v,nxt[cnt]=fir[u],fir[u]=cnt;}
inline void bfs(int s,int t) {
    q.push(s); d[s]=1; while(q.size()) { 
        R u=q.front(); q.pop();
        for(R i=fir[u];i;i=nxt[i]) { R v=vr[i];
            if(flg[v]&&!d[v]) d[v]=d[u]+1,q.push(v);
        }
    }
}
}e1,e2;
inline void main() {
    n=g(),m=g(); for(R i=1,u,v;i<=m;++i) 
        u=g(),v=g(),e1.add(u,v),e2.add(v,u);
    s=g(),t=g(); for(R i=1;i<=n;++i) flg[i]=1;
    e2.bfs(t,s); if(!e2.d[s]) return (void) puts("-1");
    for(R u=1;u<=n;++u) { flg[u]=e2.d[u];
        if(flg[u]) for(R i=e1.fir[u];i;i=e1.nxt[i]) { 
            R v=e1.vr[i]; if(!e2.d[v]) {flg[u]=false; break;}
        }
    }
    e1.bfs(s,t); if(e1.d[t]) printf("%d\n",e1.d[t]-1);
    else puts("-1");
}

P2312 解方程:模意义下解方程。。。实在不行就双哈希。秦九昭。

2015

P2615 神奇的幻方:模拟。

P2661 信息传递:并查集判环。

P2668 斗地主:

P2540 斗地主增强版:在写正解了。

P2678 跳石头:二分 + 贪心。

P2679 子串:DP,设 \(f[i][j][k][0/1]\) 表示前 \(i\) 位匹配前 \(j\) 位,\(k\) 个子串,\(i\) 是否选了。

P2680 运输计划:先二分将问题转化为判定,现在就是要求是否存在 去掉一条边,使得所有路径的最大值小于二分的答案。
于是可以树上差分。

常数被锤

const int N=300010,L=18;
struct edge {
    int u,v,w,t;
}e[N];
int n,m,cnt,mx;
int vr[N<<1],nxt[N<<1],w[N<<1],W[N],fir[N],d[N],dis[N],f[N][L+1],c[N],s[N];
inline void add(int u,int v,int ww) 
    {vr[++cnt]=v,w[cnt]=ww,nxt[cnt]=fir[u],fir[u]=cnt;}
inline void dfs(int u) {
    for(R i=fir[u];i;i=nxt[i]) { R v=vr[i];
        if(d[v]) continue; W[v]=w[i];
        f[v][0]=u; d[v]=d[u]+1; dis[v]=dis[u]+w[i];
        dfs(v);
    }
}
inline int lca(int u,int v) {
    if(d[u]<d[v]) swap(u,v);
    for(R i=L;~i;--i) if(d[f[u][i]]>=d[v]) u=f[u][i];
    if(u==v) return u;
    for(R i=L;~i;--i) if(f[u][i]!=f[v][i]) u=f[u][i],v=f[v][i];
    return f[u][0];
}
inline bool ck(int x) { R tot=0;
    memset(c,0,sizeof(c));
    for(R i=1;i<=m;++i) if(e[i].w>x) 
        ++tot,++c[e[i].u],++c[e[i].v],c[e[i].t]-=2;
    for(R i=1;i<=n;++i) c[f[s[i]][0]]+=c[s[i]];
    for(R i=n;i;--i) if(tot==c[i]&&mx-W[i]<=x) return true;
    return false;
}
inline bool cmp(const int& a,const int& b) {return d[a]>d[b];}
inline void main() {
    n=g(),m=g(); 
    for(R i=1,u,v,w;i<n;++i) 
        u=g(),v=g(),w=g(),add(u,v,w),add(v,u,w);
    d[1]=1; dfs(1);
    for(R t=1;t<=L;++t) for(R i=1;i<=n;++i) f[i][t]=f[f[i][t-1]][t-1];
    for(R i=1;i<=n;++i) s[i]=i; sort(s+1,s+n+1,cmp);
    for(R i=1;i<=m;++i) {
        e[i].u=g(),e[i].v=g();
        e[i].w=dis[e[i].u]+dis[e[i].v]-2*dis[lca(e[i].u,e[i].v)];
        e[i].t=lca(e[i].u,e[i].v);
        mx=max(mx,e[i].w);
    } R l=0,r=mx+1;
    while(l<r) {
        R md=l+r>>1; 
        if(ck(md)) r=md;
        else l=md+1;
    } printf("%d\n",l);
}

2016

P1563 玩具谜题:模拟。

P1600 天天爱跑步:转化条件,转化为深度相关的一些量,然后全局桶维护。其实类似差分思想。

P1850 换教室:

P2831 愤怒的小鸟:状压,把是否杀死了某只猪压起来,先预处理 \(i,j\) 两点构成的抛物线覆盖的点集,然后转移时注意我们强制从没有死的最低位的猪转移,即强制杀死没有死的最低位的猪。注意这样尽管有些状态没有被转移到,但是有句话说得好:

即最终的状态是一定能够转移到。

这个思想还是很重要的。

int T,n,m,f[1<<L],s[L][L],low[1<<L]; double x[L],y[L];
inline void calc(double& A,double& B,double x1,double y1,double x2,double y2) {
    A=(x2*y1-x1*y2)/(x1*x2*(x1-x2)),B=(x2*x2*y1-x1*x1*y2)/(x1*x2*(x2-x1));
}
inline bool ck0(const double& x) {return x<eps&&x>-eps;}
inline void main() {
    for(R i=1,p=1;i<(1<<L);++i) { p=0;
        while((i>>p&1)) ++p; low[i]=p;
    }
    T=g(); while(T--) {
        memset(f,0x3f,sizeof f),memset(s,0,sizeof s),f[0]=0; n=g(),g();
        for(R i=0;i<n;++i) scanf("%lf%lf",&x[i],&y[i]);
        for(R i=0;i<n;++i) for(R j=0;j<n;++j) {
            if(ck0(x[i]-x[j])) continue; register double a,b;
            calc(a,b,x[i],y[i],x[j],y[j]); 
            if(a>0) continue; 
            for(R k=0;k<n;++k) if(ck0(y[k]-a*x[k]*x[k]-b*x[k])) s[i][j]|=(1<<k);
        } 
        for(R i=0,lim=(1<<n),p;i<lim;++i) { p=low[i]; 
            f[i|(1<<p)]=min(f[i|(1<<p)],f[i]+1);
            for(R k=0;k<n;++k) f[i|s[p][k]]=min(f[i|s[p][k]],f[i]+1);
        } printf("%d\n",f[(1<<n)-1]);
    }
}

猜你喜欢

转载自www.cnblogs.com/Jackpei/p/11761634.html