NOIP 模拟题 简单题 随便做 题解与总结

来源:互联网 发布:矩阵微分方程 编辑:程序博客网 时间:2024/05/21 06:19

简单题”题解

  • T1 简

题解:
水题啊,排个序取奇数位的数就可以了。
代码:

#include<cstdio>#include<algorithm>using namespace std;typedef long long ll;const ll size = 100005;ll n,ans = 0;ll da[2*size];//数组大小啊!!!int main(){    scanf("%lld",&n);       for (ll i = 1;i <= 2 * n;i++)    scanf("%lld",&da[i]);       sort(da + 1 , da + 1 + 2*n);    for (ll i =1;i<=2*n;i+=2)    ans += da[i];    printf("%lld",ans);     return 0;}
  • T2 单

题解:
不难啊,考虑到一条边的两端的点xyb值(设xy的父节点),可以得出其差值为n1a[i]ya[i]那么就好做了。首先,若知道a数组,则可以用此公式,先暴力算出根的b,然后用这个公式不停向下更新。若知道b,那么考虑叶节点y,其ya[i]恰好为其本身,然后就可以用一个只关于1na[i]的式子表示,然后把其更新到父节点,然后一直更新到根节点,然后根节点的子树又为0na[i]就可以解方程啦,哈哈,只用两次dfs
代码(点不进去了,就用了标程):

#include<cstdio>#include<algorithm>#include<iostream>#include<cstring>using namespace std;typedef long long ll;const int N=100010;ll T,t,n,edgenum,head[N],c[N],fa[N],dep[N],atot,ans[N],v[N];struct edge {    int v,next;} e[N<<1];struct node {    ll tot,con;} tr[N];ll read() {    ll x=0;    char ch=getchar();    while(ch<'0'||ch>'9') ch=getchar();    while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();    return x;}void addedge(int f,int t) {    e[++edgenum].v=t;    e[edgenum].next=head[f];    head[f]=edgenum;}void dfs1(int a,int pre) {    fa[a]=pre;    dep[a]=dep[pre]+1;    for(int i=head[a]; i; i=e[i].next) {        if(e[i].v==pre) continue;        dfs1(e[i].v,a);    }}inline void solve1() {    ll root_tot=0,root_con=0,sum=0;    dep[0]=-1;    dfs1(1,0);    for(int i=2; i<=n; i++) {        tr[i].con=c[fa[i]]-c[i];        tr[i].tot=1;        tr[fa[i]].tot--;        tr[fa[i]].con-=tr[i].con;    }    for(int i=2; i<=n; i++) {        root_tot+=tr[i].tot*dep[i];        root_con+=tr[i].con*dep[i];    }    atot=(2*c[1]-root_con)/root_tot;    for(int i=2; i<=n; i++) ans[i]=(tr[i].con+tr[i].tot*atot)/2,sum+=ans[i];    ans[1]=atot-sum;}void dfs2(int a,int pre) {    fa[a]=pre;    dep[a]=dep[pre]+1;    for(int i=head[a]; i; i=e[i].next) {        if(e[i].v==pre) continue;        dfs2(e[i].v,a);        v[a]+=v[e[i].v];    }}void dfs3(ll sum,int a,int pre) {    ans[a]=(sum-(v[a]<<1))+ans[pre];    for(int i=head[a]; i; i=e[i].next) {        if(e[i].v==pre) continue;        dfs3(sum,e[i].v,a);    }}inline void solve2() {    ll sum=0;    for(int i=1; i<=n; i++) v[i]=c[i],sum+=c[i];    dep[0]=-1;    dfs2(1,0);    for(int i=2; i<=n; i++) ans[1]+=c[i]*dep[i];    for(int i=head[1]; i; i=e[i].next) dfs3(sum,e[i].v,1);}int main() {    int a,b;    T=read();    while(T--) {        edgenum=0;        memset(ans,0,sizeof ans);        memset(head,0,sizeof head);        n=read();        for(int i=1; i<n; i++) {            a=read(),b=read();            addedge(a,b),addedge(b,a);        }        t=read();        for(int i=1; i<=n; i++) c[i]=read();        if(t) solve1();        else solve2();        for(int i=1; i<=n; i++) printf("%lld ",ans[i]);        putchar('\n');    }    return 0;}
  • T3 题

题解:
(见我另一篇博客:
http://blog.csdn.net/Demon_Rieman/article/details/78066943)

“随便做”题解

  • T1 随

题解:
(好高级的做法) 明显我们要用dp,一开始想的二维dp,后来看存不下。所以这题只用一个一维dp就可以了。先说一下二维的,dp[i][j]表示选j次,下余i的期望,p[i]表示只选一次,选到i的期望,然后转移很明显,就是dp[i+1][j]=(0m<moddp[i][m]p[m])mod1000000007,这样时间复杂度是O(mod2logm)的,空间复杂度是O(mod)
都不行,于是我们观察,发现每次都要用 来转移,并且每次转移方式都是一样的,那不就是???对啦,递归的,也就是说,实际上答案就是的次方(类似矩阵),然后就写一个伪.矩阵快速幂就可以了,这样空间,时间,可以过。(也有人写滚动数组,但我不熟啊,所以就写了一个不滚动的。但是滚动要快一倍……)
代码:

#include<cstdio>#include<cstring>using namespace std;typedef long long ll;const ll size = 1005;const ll MOD = 1000000007;ll n , m, mode;ll ans[size] , replace[size] ,t[size];ll q_pow(ll a,ll b,ll c){    ll res = 1;    while (b)    {        if (b%2) (res*=a)%=c;         b/=2;        (a*=a)%=c;    }    res %= c;    return res;}ll inv(ll a,ll b)//b 是质数 {    return q_pow(a , b - 2 ,MOD);}void mat_mult(ll *a , ll b[]){    ll tmp[size];memset(tmp,0,sizeof tmp);    for (ll i = 1;i < mode;i++)    for (ll j = 1;j < mode;j++)    ( tmp[ (i * j) % mode ] += a[i] * b[j] )%= MOD;    for (ll i = 1;i < mode;i++)    a[i] = tmp[i];}void mat_q_pow(ll *a,ll *b,ll m)//答案 mod c {    a[1] = 1;    while (m)    {        if (m%2==1) mat_mult(a,b);        m/=2;        mat_mult(b,b);    }}int main(){    scanf("%lld%lld%lld",&n,&m,&mode);    for (int i = 1;i<=n;i++)    {        ll x;        scanf("%lld",&x);        t[x]++;    }    ll invn = inv( n , MOD);    for (int i = 1;i < mode;i++)    replace[i] = (t[i] * invn)%MOD ;    //for (int i = 0;i<mode;i++)    //printf("%lld " , replace[i]);    mat_q_pow(ans , replace , m );    ll tot = 0;    for (ll i= 1;i < mode;i++)    tot = (tot + i * ans[i])%MOD;    (tot += MOD)%=MOD;    printf("%lld",tot);}

- T2 便

题解:
限制条件推一推就变成了对于每一行,其增量相同,也就是对于同一行的 mat[k][i]mat[k][j]对于每一个k都相等(当然列也如此),所以我们只需做一个带权并查集就可以了。如何保证不是负数?只需要每次接父节点时都接小的,然后看这个加上增量是不是负的就行了。
代码:

#include <bits/stdc++.h>typedef long long ll;const ll size =200201;using namespace std;int rank[size], twoo[size], mi[size], fa[size],_r, _c, n,T;struct ob {    int ri, ci, rank;    bool operator <(const ob &a) const    { return ci < a.ci; }}obb[size];int findfa(int x) {if (fa[x] == x) return x;int rt = findfa(fa[x]);        rank[x] += rank[fa[x]];        fa[x] = rt;    return fa[x];}int unionm(int a,int b,int ci){    int faa = findfa(a),fab = findfa(b);    if (rank[faa]>rank[fab]) fa[faa]  = fab;    if (rank[faa] == rank[fab]) rank[faa]++;}bool merge(int a, int b, int ci) {    int ra = findfa(a), rb = findfa(b);        if (ra != rb)         {        fa[ra] = rb;rank[ra] = ci - rank[a] + rank[b];        return 1;        }        else     {    bool yk= (rank[a] == rank[b] + ci);    return yk;    }}bool panding() {        for(int i= 0;i<= n;i++)         {        fa[i] = i;        rank[i] = 0;         }sort(obb + 1, obb + 1 + n);    for(int i= 1;i<= n - 1;i++) if(obb[i].ci == obb[i + 1].ci)     if(!merge(obb[i].ri, obb[i + 1].ri, obb[i + 1].rank - obb[i].rank)) return false;memset(twoo, 0x3f, sizeof(twoo));memset(mi, 0x3f, sizeof(mi));for(int i= 1;i<= n;i++) {    int rt = findfa(obb[i].ri);    twoo[rt] = min(twoo[rt], obb[i].rank + rank[obb[i].ri]);}    for(int i=0;i<=_r;i++)     {        int rt = findfa(i);        mi[rt] = min(mi[rt], -rank[i]);    }    for(int i=0;i<= _r;i++)     if (fa[i] == i && (twoo[i] + mi[i] < 0))     return 0;    return 1;}int check = 1;int main(){    scanf("%d",&T);while(T--){    check = 1;    scanf("%d%d%d",&_r,&_c,&n);    for(int i=1;i<=n;i++)        scanf("%d%d%d",&obb[i].ri,&obb[i].ci,&obb[i].rank);        for(int i= 0;i<= n;i++)         if (obb[i].rank < 0) check = 0;    if (panding()&&check)    printf( "Yes\n");    else printf("No\n");    }       }    return 0;}

- T3 做

(惊天好题,这么好的题目背景,肯定要截下来)
这里写图片描述
题解:
实际是水题,膜法师肯定不会动,所以他只会膜或者喊:“苟利国家生死以,竹外桃花三两枝。”恢复,而香港记者则每次使abs(xx)abs(yy)中较大的减少,因为要使平方和最小。模拟一遍就行了。
代码:

#include<cstdio>#include<algorithm>#include<cstring>using namespace std;int T;int main(){    scanf("%d",&T);    int a,b;    scanf("%d%d",&a,&b);    while (T--)    {        int x1,x2,y1,y2;        int  c ,d;        scanf("%d%d%d%d%d%d" , &x1 , &y1 , &x2 , &y2 , &c , &d);        int dx = abs(x1-x2) , dy = abs(y1-y2);        int tot = 0;        while (dx > 0 || dy > 0)        {            if (c < a) c+=b;            else {tot += dx*dx + dy*dy; c -= a;}            if (tot >= d) break;            if (dx > dy) dx--;else dy--;            if (dx > dy) dx--;else dy--;        }        if (tot>=d) printf("NAIVE\n");        else printf("SIMPLE\n");    }    return 0;}

总结:

  • day1

T1 数组开小了,气啊。下次一定要好好注意啊,考试时可不能犯这种错误。
T2 考试时没怎么想,连30的暴力都没写,主要是对树的题还是不是很熟。
T3 我推了好久啊,但也没推出正解,知识水平还不够啊,组合数学要多学学,虽然数学我最不好的就是组合数学,最后写暴力dp还得了80分,所以应该好好调整做题时间,想很久想不出来先把暴力写了,然后把题做完再来看这道题。(这题想了2个小时,但暴力5多分钟时候就想到了,然后就这样浪费了1小时50分钟,如果拿来做第二题,还能得30分)。

  • day2

T1 做不来,就只写了一个20分的骗分,结果这都只骗到10分。 如果多想想还是有可能想出来的。然后改的时候居然模数搞错了,害的只有20,改了一下午才发现…….
T2 跟day1的T3一样,花了我大多时间,一开始想正解,没想出来(以前基本没用过带权并查集),然后写90分的暴力,就是强行记录差以及强行把矩阵做出来,但不知为何只有10,码力不足啊,一定是哪写错了。
T3 千古奇冤啊,我写平方时,居然写成了数学的,没想到样例还能过,然后这题又简单,就没去造数据了。这题告诉我再简单的题也要造点数据验证。
很重要的:考试时一定要注意数组大小,符号,模数。

原创粉丝点击