您的位置:首页 > 其它

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

2017-10-17 15:01 513 查看

简单题”题解

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 单

题解:

不难啊,考虑到一条边的两端的点x,y的b值(设x为y的父节点),可以得出其差值为∑n1a[i]−∑y的子树a[i]那么就好做了。首先,若知道a数组,则可以用此公式,先暴力算出根的b,然后用这个公式不停向下更新。若知道b,那么考虑叶节点y,其∑y的子树a[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
,c
,fa
,dep
,atot,ans
,v
;

struct edge {
int v,next;
} e[N<<1];

struct node {
ll tot,con;
} tr
;

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]=(∑0≤m<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;
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;
}


[b]- T3 做


(惊天好题,这么好的题目背景,肯定要截下来)



题解:

实际是水题,膜法师肯定不会动,所以他只会膜或者喊:“苟利国家生死以,竹外桃花三两枝。”恢复,而香港记者则每次使abs(x记者−x你)与abs(y记者−y你)中较大的减少,因为要使平方和最小。模拟一遍就行了。

代码:

#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 千古奇冤啊,我写平方时,居然写成了数学的,没想到样例还能过,然后这题又简单,就没去造数据了。这题告诉我再简单的题也要造点数据验证。

很重要的:考试时一定要注意数组大小,符号,模数。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息