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

简单题”题解

  • T1 简

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

#include
#include
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 单

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

#include
#include
#include
#include
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; iread(),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
#include
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
    //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 
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 做

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

#include
#include
#include
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 千古奇冤啊,我写平方时,居然写成了数学的,没想到样例还能过,然后这题又简单,就没去造数据了。这题告诉我再简单的题也要造点数据验证。
很重要的:考试时一定要注意数组大小,符号,模数。

你可能感兴趣的:(考试题解,noip模拟题,考试题解)