NOIP2014提高组题解

\(D1T1\) 生活大爆炸版石头剪刀布 \((OK)\)

\(D1T2\) 联合权值 \((OK)\)

\(D1T3\) 飞扬的小鸟 \((OK)\)

\(D2T1\) 无线网络发射器选址 \((OK)\)

\(D2T2\) 寻找道路 \((OK)\)

\(D2T3\) 解方程 \((OK)\)

这一年姑且算是完结撒花了,题目还比较简单,大概是没有什么高深的算法和恶心的数据结构题吧.

\(D1T1\)直接开个二维数组,存下每种情况的得分.然后把两个人的出拳都补充到\(n\),然后模拟讨论即可.

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<map>
#include<set>
#define ll long long
using namespace std;
inline int read(){
    int x=0,o=1;char ch=getchar();
    while(ch!='-'&&(ch<'0'||ch>'9'))ch=getchar();
    if(ch=='-')o=-1,ch=getchar();
    while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar();
    return x*o;
}
const int N=1005;
int n,na,nb,ans1,ans2;
int a[N],b[N],c[5][5];
int main(){
    n=read();na=read();nb=read();
    for(int i=1;i<=na;++i)a[i]=read();
    for(int i=1;i<=nb;++i)b[i]=read();
    c[0][1]=c[0][4]=0;c[0][2]=c[0][3]=1;
    c[1][2]=c[1][4]=0;c[1][0]=c[1][3]=1;
    c[2][0]=c[2][3]=0;c[2][1]=c[2][4]=1;
    c[3][0]=c[3][1]=0;c[3][2]=c[3][4]=1;
    c[4][2]=c[4][3]=0;c[4][0]=c[4][1]=1;
    for(int i=1;i<=na;++i){
        for(int j=1;i+j*na<=n;++j)a[i+na*j]=a[i];
    }
    for(int i=1;i<=nb;++i){
        for(int j=1;i+j*nb<=n;++j)b[i+nb*j]=b[i];
    }
    for(int i=1;i<=n;++i){
        ans1+=c[a[i]][b[i]];
        ans2+=c[b[i]][a[i]];
    }
    printf("%d %d\n",ans1,ans2);
    return 0;
}

\(D1T2\)不久前做过.一棵树上距离为2的点对很容易想到是与同一个节点相连的两个节点,我们枚举这样的"一个节点"来找到所有满足条件的点对.

首先考虑特殊情况,若一个节点u只有两个节点v_1,v_2与它相连,那么这个节点产生的贡献就是\(w[v_1]*w[v_2]+w[v_2]*w[v_1]\)(注意是有序点对,所以会产生两倍贡献),即\(2*w[v_1]*w[v_2]\),即(\(w[v_1]+w[v_2])^2-w[v_1]^2-w[v_2]^2\).若一个节点u只有三个节点\(v_1,v_2,v_3\)与它相连,那么这个节点产生的贡献就是\(2*w[v_1]*w[v_2]+2*w[v_1]*w[v_3]+2*w[v_2]*w[v_3]\)=\((w[v_1]+w[v_2]+w[v_3])^2-w[v_1]^2-w[v_2]^2-w[v_3]^2.\)

通过这两个特殊情况我们可以得到结论,对于一个节点\(u\),设有\(k\)个节点与它相连,则节点\(u\)产生的贡献就是\((\sum_{i=1}^k(w[v_i]))^2-\sum_{i=1}^kw[v_i]^2.\)

那么我们直接\(O(n)\)遍历每一个节点,然后遍历所有与它相连的节点,对于最大联合权值只要在遍历相连节点的时候,找到两个权值最大的节点即可,而对于联合权值之和就按照上面那个公式分成前后两部分先分别计算,最后相减统计入答案即可.

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<map>
#include<set>
#define ll long long
using namespace std;
inline int read(){
    int x=0,o=1;char ch=getchar();
    while(ch!='-'&&(ch<'0'||ch>'9'))ch=getchar();
    if(ch=='-')o=-1,ch=getchar();
    while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar();
    return x*o;
}
const int mod=10007;
const int N=2e5+5;
int ans1,ans2,val[N];
int tot,head[N],nxt[N<<1],to[N<<1];
inline void add(int a,int b){nxt[++tot]=head[a];head[a]=tot;to[tot]=b;}
int main(){
    int n=read();
    for(int i=1;i<n;++i){
        int a=read(),b=read();
        add(a,b);add(b,a);
    }
    for(int i=1;i<=n;++i)val[i]=read();
    for(int u=1;u<=n;++u){
        int max1=0,max2=0;ll tot1=0,tot2=0;
        for(int i=head[u];i;i=nxt[i]){
            int v=to[i];
            if(val[v]>max1)max2=max1,max1=val[v];
            else if(val[v]>max2)max2=val[v];
            tot1=(tot1+val[v])%mod;tot2=(tot2+val[v]*val[v]%mod)%mod;
        }
        ans1=max(ans1,max1*max2);
        ans2=(ans2+tot1*tot1%mod-tot2+mod)%mod;
    }
    printf("%d %d\n",ans1,ans2);
    return 0;
}

\(D1T3\)不久前做过,写过博客.

\(D2T1\)刚开始还想着什么二维前缀和,一看数据范围直接暴力枚举每个路口,然后暴力统计在每个路口安装能够覆盖得到的公共场所即可,时间复杂度\(O(128^4)\).

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<map>
#include<set>
#define ll long long
using namespace std;
inline int read(){
    int x=0,o=1;char ch=getchar();
    while(ch!='-'&&(ch<'0'||ch>'9'))ch=getchar();
    if(ch=='-')o=-1,ch=getchar();
    while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar();
    return x*o;
}
int w[200][200];
int main(){
    int d=read(),n=read();
    for(int i=1;i<=n;++i){
        int x=read(),y=read(),z=read();
        ++x;++y;w[x][y]+=z;
    }
    int ans1=0,ans2=0;
    for(int i=1;i<=129;++i){
        for(int j=1;j<=129;++j){//枚举路口,如果你从什么i+d,j+d开始枚举,就会获得70分的好成绩
            int cnt=0;
            for(int k=max(1,i-d);k<=min(i+d,129);++k)
                for(int l=max(1,j-d);l<=min(j+d,129);++l)cnt+=w[k][l];
            if(cnt==ans1)++ans2;
            if(cnt>ans1)ans1=cnt,ans2=1;
        }
    }
    printf("%d %d\n",ans2,ans1);
    return 0;
}

\(D2T2\)手玩第二个样例,可以发现:建反图后,从入度为\(0\)的非终点的点出发,跑拓扑排序,把图中经过的点都打上标记,这些点在正图中都没有直接或间接与终点相连,所以都不能走.标记完之后,继续在反图上从终点开始跑最短路即可.

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<map>
#include<set>
#define ll long long
using namespace std;
inline int read(){
    int x=0,o=1;char ch=getchar();
    while(ch!='-'&&(ch<'0'||ch>'9'))ch=getchar();
    if(ch=='-')o=-1,ch=getchar();
    while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar();
    return x*o;
}
const int N=10005;
const int M=200005;
int n,m,s,t,deg[N],visit[N],bj[N],dis[N];
queue<int>q;vector<int>g[N];
int main(){
    n=read();m=read();
    for(int i=1;i<=m;++i){
        int a=read(),b=read();
        if(a==b)continue;
        g[b].push_back(a);++deg[a];
    }
    for(int i=1;i<=n;++i)sort(g[i].begin(),g[i].end());
    s=read();t=read();
    for(int i=1;i<=n;++i){
        if(!deg[i]&&i!=t){
            q.push(i);bj[i]=1;
        }
    }
    while(q.size()){
        int u=q.front();q.pop();int last=-1;//last避免走重边
        for(int i=0;i<(int)g[u].size();++i){
            int v=g[u][i];if(v==last)continue;
            last=v;--deg[v];bj[v]=1;
            if(!deg[v])q.push(v);
        }
    }
    memset(dis,0x3f,sizeof(dis));dis[t]=0;
    q.push(t);
    while(q.size()){
        int u=q.front();q.pop();
        for(int i=0;i<(int)g[u].size();++i){
            int v=g[u][i];if(bj[v])continue;
            if(dis[v]>dis[u]+1){
                dis[v]=dis[u]+1;
                if(!visit[v])visit[v]=1,q.push(v);
            }
        }
    }
    if(dis[s]<N)printf("%d\n",dis[s]);
    else puts("-1");
    return 0;
}

\(D2T3\)因为机房大佬前几天在旁边讨论了一下这道题,我听到了诸如秦九韶算法,玄学取模这样的关键字,所以今天做的时候就一直往这方面想的.这也算变相地看题解了吧...

玄学取模用于\(a_i\)的读入,一边读入一边取模,每天一遍防止高精.为什么是玄学呢?因为我本来模数用的\(19260817\)发现过不去,改成\(1e9+7\)就能过了.

秦九韶算法用于枚举\(x(1<=x<=m)\)时能够\(O(n)\)计算,保证时间复杂度为\(O(nm)\),如果每次快速幂带了个\(log\)是过不去的.毕竟我没写快速幂,还要卡常才能过.

哦哦,秦九韶算法我也是在高中必修三刚学的,大概讲一下就是\(a_0+a_1x+a_2x^2+\cdots+a_nx^n\)=\((((a_nx+a_{n-1})x+a_{n-2})x+...+a_1)x+a_0\),一个\(O(n)\)循环就能计算这个多项式了.

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<map>
#include<set>
#define rg register
#define mod 1000000007
#define ll long long
using namespace std;
inline ll read(){
    rg ll x=0,o=1;char ch=getchar();
    while(ch!='-'&&(ch<'0'||ch>'9'))ch=getchar();
    if(ch=='-')o=-1,ch=getchar();
    while(ch>='0'&&ch<='9')x=((x<<3)+(x<<1)+ch-'0')%mod,ch=getchar();
    return x*o;
}
const int N=105;
const int M=1e6+5;
int n,m,tot,ans[M];ll a[N];
int main(){
    n=read();m=read();for(rg int i=0;i<=n;++i)a[i]=read();
    for(rg int x=1;x<=m;++x){
        rg ll now=0;
        for(rg int i=n;i>=1;--i)
            now=(1ll*((now+a[i])%mod)*x)%mod;
        if(now+a[0]==0)ans[++tot]=x;
    }
    printf("%d\n",tot);
    for(rg int i=1;i<=tot;++i)printf("%d\n",ans[i]);
    return 0;
}
上一篇:蒟蒻们的自闭题单


下一篇:208. 实现Trie(前缀树)