NOIP2018模拟题解集

序列

给定N、A、B,构造一个长度为N的排列。
使得其最长上升子序列长度为A,最长下降子序列长度为B
n<=

观察N=AB的情况,可以发现是 3 2 1 6 5 4 9 8 7 3_{2_1}^{{6_{5_4}}^{9_{8_7}}} 这样子的,组数是A,个数是B。
于是A
B<N时或N<A+B-1无解.
其他时候每组的B不塞满就可以了。

这题重点并不是怎么想而是怎么写!!!!!这种模拟题有一种常见的减少思维量的技巧,就是一边做一边 n n-- ,用剩下的 n n 去思考可以省事很多。(一位代码写炸的人的自我反省)

代码

#include <bits/stdc++.h>
#define maxn 100005
#define MAXN 1000005
#define INF 0x3f3f3f3f
#define LL long long
using namespace std;
LL read(){
    LL res,f=1; char c;
    while(!isdigit(c=getchar())) if(c=='-') f=-1; res=c^48;
    while(isdigit(c=getchar())) res=(res<<3)+(res<<1)+(c^48);
    return res*f;
}
int n,a,b,T;
int main(){
    T=read();
    while(T--){
        n=read(); a=read(); b=read();
        if(n>a*b || n<a+b-1){puts("No"); continue;}
        puts("Yes");
        while(n){
            int x=min(a,n-b+1);
            for(int i=n-x+1;i<=n;i++) printf("%d ",i);
            b--;
            n-=x;
        }
        puts("");
    }
    return 0;
}

购物

visit_world 有一个商店,商店里卖N个商品,第i 个的价格为a[i]。我们称一个正整数K 是美妙的,当且仅当我们可以在商店里选购若干个商品,使得价格之和落在区间 [K,2K]中。
问:有多少个美妙的数。

考虑一个价格 a i a_i 它能影响到 [ a i + 1 2 a i ] [\frac{a_i+1}{2},a_i] 假如现在有了n个答案区间,那么每个答案区间的右端点+ a i a_i 就是更新区间了。这样其实这样就能做了,但是我们继续将这道奇技淫巧。
假如之前的区间连续。首先分类讨论a[i],设现在处理到了 r r 表示最右端点。
如果 a i a_i < r r r &gt; ( r + a i ) / 2 r&gt;(r+a_i)/2 所以新加一个 a i a_i 的时候直接 a n s + = a i ans+=a_i 就行了,也就是突出来的长度。
如果 a i &gt; r a_i&gt;r ,那么 r &lt; ( r + a i ) / 2 r&lt;(r+a_i)/2 ,也就是会空出来一个长度,剪掉就行。
如果不连续,首先那个区间的右端点一定是某个前缀和设 a + b a+b ,那么下一段的开始假设是 ( a + b + c ) / 2 (a+b+c)/2 ,那么新的靠左区间的右端点是 a + b + x / 2 a+b+x/2 下一个是 ( a + b + c ) / 2 (a+b+c)/2 大,碰到时 a + b + x / 2 &lt; ( a + b + c ) / 2 a+b+x/2&lt;(a+b+c)/2 a + b + x &lt; c a+b+x&lt;c 那么我们只要让 x &gt; c x&gt;c 就可以保证永远碰不到了。那么就 a i a_i 排序(可以理解成一跑跑太远)

代码

#include <bits/stdc++.h>
#define maxn 100005
#define MAXN 1000005
#define INF 0x3f3f3f3f
#define LL long long
using namespace std;
LL read(){
    LL res,f=1; char c;
    while(!isdigit(c=getchar())) if(c=='-') f=-1; res=c^48;
    while(isdigit(c=getchar())) res=(res<<3)+(res<<1)+(c^48);
    return res*f;
}
int n,m,a[maxn];
LL sum[maxn];
LL ans;
int main(){
    n=read();
    for(int i=1;i<=n;i++) a[i]=read();
    sort(a+1,a+n+1);
    for(int i=1;i<=n;i++) sum[i]=a[i]+sum[i-1];
    for(int i=2;i<=n;i++){
        if((a[i]+1>>1)>sum[i-1]) ans-=(a[i]+1>>1)-sum[i-1]-1;
    }
    ans+=sum[n]-(a[1]+1>>1)+1;
    printf("%lld\n",ans);
    return 0;
}

计数

给定一棵先序遍历序是编号序的数,给定M个条件要求u的中序遍历小于v,求方案数n<=400,m<=10e3

树的遍历
先序遍历的性质是一个子数的先序遍历序号是连续的,且第一个为根,考虑DP,枚举两棵树的先序遍历分界线,也就是斯特林数。那么思考一下带条件的斯特林数。
中序遍历的性质是一个左子树的所有小于根小于右子树的所有,因为左右子树和根都是一段连续的区间,两个区间的关系可以使用二维前缀和标记。于是记忆化搜索,枚举分界点,注意,左子树或右子树为空的情况需要考虑进去。

代码

#include <bits/stdc++.h>
#define maxn 405
#define mod 1000000007
#define INF 0x3f3f3f3f
#define LL long long
using namespace std;
LL read(){
LL res,f=1; char c;
	while(!isdigit(c=getchar())) if(c=='-') f=-1; res=c^48;
	while(isdigit(c=getchar())) res=(res<<3)+(res<<1)+(c^48);
	return res*f;
}
int T,n,m;
LL f[maxn][maxn],sum[maxn][maxn];
bool A(int a,int c,int b,int d){
	if(a>c || b>d) return 0;
	return sum[c][d]+sum[a-1][b-1]-sum[a-1][d]-sum[c][b-1]>0;
}
LL DFS(int x,int y){
	if(~f[x][y]) return f[x][y];
	LL res=0;
	if(x>=y) return 1;
	for(int i=x;i<=y;i++){
		if(!A(i+1,y,x+1,i)&&!A(x,x,x+1,i)&&!A(i+1,y,x,x)) res=(res+DFS(x+1,i)*DFS(i+1,y)%mod)%mod;
	}
	return f[x][y]=res;
}
int main(){
	T=read();
	while(T--){
		n=read(); m=read();
		memset(f,-1,sizeof f);
		memset(sum,0,sizeof sum);
		for(int i=1;i<=m;i++){
			sum[read()][read()]=1;
		}
		for(int i=1;i<=n;i++){
			for(int j=1;j<=n;j++){
				sum[i][j]+=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1];
			}
		}
		printf("%lld\n",DFS(1,n));
	}
	return 0;
}

Matrix

给定 f [ 1 ] [ i ] f[1][i] f [ i ] [ 1 ] f[i][1] f [ n ] [ m ] = a f [ n 1 ] [ m ] + b f [ n ] [ m 1 ] f[n][m]=a*f[n-1][m]+b*f[n][m-1]
f [ n ] [ n ] f[n][n]

考虑每一个 f [ i ] [ 1 ] f[i][1] f [ 1 ] [ i ] f[1][i] 会被 f [ n ] [ n ] f[n][n] 取到几次,因为 f [ n ] [ n ] f[n][n] 会向左下角沿一条路走到 f [ i ] [ j ] f[i][j] ,系数就是路线的方案数。每一个位置会向下和向左走的步数是确定的,所以 a a b b 的指数是确定的,于是就可以跑了,具体的见代码吧。

代码

#include <bits/stdc++.h>
#define maxn 200005
#define mod 1000000007
#define LL long long
#define INF 0x3f3f3f3f
using namespace std;
LL read(){
    LL res,f=1; char c;
    while(!isdigit(c=getchar())) if(c=='-') f=-1; res=c^48;
    while(isdigit(c=getchar())) res=(res<<3)+(res<<1)+(c^48);
    return res*f;
}
LL Pow(LL x,LL k){
    LL res=1;
    while(k){
        if(k&1) res=(res*x)%mod;
        k>>=1;
        x=(x*x)%mod;
    }
    return res;
}
LL n,a,b,ans,l[maxn],t[maxn],jc[maxn];
LL C(LL b,LL a){
    return jc[b]*Pow(jc[a],mod-2)%mod*Pow(jc[b-a],mod-2)%mod;
}
int main(){
    freopen("matrix.in","r",stdin);
    freopen("matrix.out","w",stdout);
    jc[0]=1;
    for(int i=1;i<maxn;i++) jc[i]=jc[i-1]*i%mod;
    n=read(); b=read(); a=read();
    l[1]=read();
    for(int i=2;i<=n;i++){
        ans=(ans+(l[i]=read())*C(2*n-2-i,n-2)%mod*Pow(b,n-1)%mod*Pow(a,n-i)%mod)%mod;
    }
    t[1]=read();
    for(int i=2;i<=n;i++){
        ans=(ans+(t[i]=read())*C(2*n-2-i,n-2)%mod*Pow(a,n-1)%mod*Pow(b,n-i)%mod)%mod;
    }
    printf("%lld\n",ans);
    return 0;
}

Graph

求包含1的最小环,不能走一下就回来。

考虑朴素的方法,把1拆成出点和入点,出点到入点的最短路就是答案了。然而这样不能避免走一下就回来的问题,考虑枚举,那么我们有两种方案。
一种是枚举中间的某条边,从1开始分别沿正边和反边跑,每次统计答案的时候看两条dis是不是从一个点出来的,如果不是就记ans,这样的效率是两个Dijstra
另一种是枚举开头的两个点,但是这样枚举的复杂度是 n 2 n^2 ,那么我们可以把点平分成两部分 S , T S,T ,这样是对于 u S , v T u\in S, v\in T ( u , v ) (u,v) 对我们一边Dijstra统计完了他们的答案,考虑怎样分割使得所有不等的 ( u , v ) (u,v) 对都被包括,二进制拆分,把每一位不同的拆成两部分这样就可以用 l o g   n log\ n 的效率完成枚举了总效率 O ( n l o g 2 n ) O(nlog^2n)

代码

#include <bits/stdc++.h>
#define maxn 100005
#define MAXN 200005
#define LL long long
#define INF 1e9
using namespace std;
LL read(){
    LL res,f=1; char c;
    while(!isdigit(c=getchar())) if(c=='-') f=-1; res=c^48;
    while(isdigit(c=getchar())) res=(res<<3)+(res<<1)+(c^48);
    return res*f;
}
LL dis[maxn];
struct NODE{
    int x,y;
    bool operator < (const NODE &rhs)const{
        return y>rhs.y;
    }
};
struct EDGE{
    int u,v,w,nxt;
}e[MAXN];
int cnt=1,head[maxn];
void add(int u,int v,int w){
    e[++cnt]=(EDGE){u,v,w,head[u]};
    head[u]=cnt;
}
priority_queue<NODE> Q;
void Dijstra(int s){
    memset(dis,0x3f,sizeof dis);
    Q.push((NODE){s,0}); dis[s]=0;
    while(!Q.empty()){
        NODE u=Q.top(); Q.pop();
        if(u.y!=dis[u.x]) continue;
        for(int i=head[u.x];~i;i=e[i].nxt){
            int v=e[i].v,w=e[i].w;
            if(dis[u.x]+w<dis[v]){
                dis[v]=dis[u.x]+w;
                Q.push((NODE){v,dis[v]});
            }
        }
    }
}
int n,m,u[maxn],v[maxn],w1[maxn],w2[maxn];
LL ans=INF;
int main(){
    n=read(); m=read();
    for(int i=1;i<=m;i++){
        u[i]=read(); v[i]=read(); w1[i]=read(); w2[i]=read();
    }
    int Log=log(n)/log(2)+1;
    for(int s=0;s<=Log;s++){
        cnt=0; memset(head,-1,sizeof head);
        for(int i=1;i<=m;i++){
            if(u[i]==1){
                if((v[i]>>s)&1) add(v[i],n+1,w2[i]);
                else add(1,v[i],w1[i]);
            }
            else if(v[i]==1){
                if((u[i]>>s)&1) add(u[i],n+1,w1[i]);
                else add(1,u[i],w2[i]);
            }
            else {add(u[i],v[i],w1[i]); add(v[i],u[i],w2[i]);}
        }
        Dijstra(1);
        ans=min(ans,dis[n+1]);
         
        cnt=0; memset(head,-1,sizeof head);
        for(int i=1;i<=m;i++){
            if(u[i]==1){
                if(!((v[i]>>s)&1)) add(v[i],n+1,w2[i]);
                else add(1,v[i],w1[i]);
            }
            else if(v[i]==1){
                if(!((u[i]>>s)&1)) add(u[i],n+1,w1[i]);
                else add(1,u[i],w2[i]);
            }
            else {add(u[i],v[i],w1[i]); add(v[i],u[i],w2[i]);}
        }
        Dijstra(1);
        ans=min(ans,dis[n+1]);
    }
    printf("%lld\n",ans);
    return 0;
}

circle

给定一张边权有正有负的有向图,求经过点数最小的正环

第一,正环非常不好搞,一般这种不好搞的东西考虑二分答案。
第二,跟点数有关的题目很难提前限制或者直接做,所以我们限定点数二分答案。
两种思路都指向二分答案。
于是倍增Floyd,二分即可。每一个值需要 l o g n logn 求出,所以是 l o g 2 n log^2n
但是更快的方法是用类似倍增LCA的方法做一遍 l o g n logn
因为我弱,所以我用的是 O ( n 3 l o g 2 n ) O(n^3log^2n)

代码

#include <bits/stdc++.h>
#define maxn 305
using namespace std;
int n,m,e[10][maxn][maxn],ans[2][maxn][maxn];
inline int MX(int a,int b){
    if(a>b) return a;
    else return b;
}
bool check(int p){
    int pos=0;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            ans[0][i][j]=(i==j?0:-1e9);
        }
    }
    for(int s=0;(1<<s)<=p;s++){
        if(p&(1<<s)){
            pos^=1;
            for(int i=1;i<=n;i++){
                for(int j=1;j<=n;j++){
                    ans[pos][i][j]=-1e9;
                }
            }
            for(int i=1;i<=n;i++){
                for(int j=1;j<=n;j++){
                    for(int k=1;k<=n;k++){
                        if(ans[pos^1][i][k]+e[s][k][j]>ans[pos][i][j]){
                            ans[pos][i][j]=ans[pos^1][i][k]+e[s][k][j];
                        }
                    }
                }
            }
        }
    }
    for(int i=1;i<=n;i++){
        if(ans[pos][i][i]>0) return 1;
    }
    return 0;
}
int main(){
    //freopen("in.txt","r",stdin);
    scanf("%d%d",&n,&m);
    for(int s=0;(1<<s)<=n;s++){
        for(int i=1;i<=n;i++){
            for(int j=1;j<=n;j++){
                e[s][i][j]=(i==j?0:-1e9);
            }
        }
    }
    for(int i=1,x,y,a,b;i<=m;i++){
        scanf("%d%d%d%d",&x,&y,&a,&b);
        e[0][x][y]=a; e[0][y][x]=b;
    }
    for(int s=1;(1<<s)<=n;s++){
        for(int i=1;i<=n;i++){
            for(int j=1;j<=n;j++){
                for(int k=1;k<=n;k++){
                    if(e[s-1][i][k]+e[s-1][k][j]>e[s][i][j]){
                        e[s][i][j]=e[s-1][i][k]+e[s-1][k][j];
                    }
                }
            }
        }
    }
    int l=2,r=n+1;
    while(l<r){
        int mid=l+r>>1;
        if(check(mid)) r=mid;
        else l=mid+1;
    }
    printf("%d\n",l%(n+1));
}

猜你喜欢

转载自blog.csdn.net/qq_32461955/article/details/83149807