NOIP 2017.10.24 总结+心得

来源:互联网 发布:fifaol3 超玩数据库 编辑:程序博客网 时间:2024/06/05 11:39

这里写图片描述
世界真的很大
今天的考试厄运缠身,orz
好端端的224变成了137,NOIP要真考成这样恐怕是要退役了
一些小错误和细节处理的问题,实在是。。
尽量通过模拟赛吧自己的问题测出来再即使修改
免得NOIP真的面临退役的命运233

看题先:

1。

这里写图片描述
讲道理NOIP DAY T1这个难度?反正我是不信
其实不算太难,但是真的有点打脑子
心路历程如下:
是K的倍数?想到NOIP 2016 DAY2 T1,即是说modK为0
那么处理矩阵前缀和?不行这道题可以有中间的矩阵
K不是1e9,是1e6?摆明了有文章
要么是数据结构维护值域要么就是直接开一个值域数组了
400的范围啊,n^4大暴力,n^3能过,那就是说枚举矩形的某个边界,然后根据那个数组的答案?
数组记录的是值域,而且是K以内的值,八成就是mod K之后的值。
考虑一个区域modK的值为0,就是说前后两个矩形的mod值相同!
那么用数组记录一下每个mod值出现过几次就好了

然后我天才的在每一个n^2后面加了个1e6的memset,正解被卡成暴力orz,得分100变成35orz

正解就是我这个方法了
完整代码:

#include<stdio.h>#include<cstring>using namespace std;typedef long long dnt;int n,m,K;int src[2000010],sum[500][500],mrk[500];dnt ans=0;dnt fix(dnt x){    return (x%K+K)%K;}int main(){    freopen("rally.in","r",stdin);    freopen("rally.out","w",stdout);    scanf("%d%d%d",&n,&m,&K);    for(int i=1;i<=n;i++)        for(int j=1;j<=m;j++)        {            int tmp;            scanf("%d",&tmp);            sum[i][j]=fix(sum[i-1][j]+sum[i][j-1]+tmp-sum[i-1][j-1]);        }    for(int i=1;i<=n;i++)        for(int j=i;j<=n;j++)        {            src[0]=1;            for(int k=1;k<=m;k++)            {                int tmp=fix(sum[j][k]-sum[i-1][k]);                mrk[k]=tmp;                ans+=src[tmp],src[tmp]++;            }            for(int k=1;k<=m;k++) src[mrk[k]]=0;        }    printf("%I64d\n",ans);    return 0;}/*2 3 21 2 12 1 2*/

感觉考试的时候,如果一道题一开始没什么思路,尝试把题目得到的信息全部写下来,写到纸上,看看能得出哪些二级结论,一步步扩大已知范围。
然后就是时间复杂度一定要慎重,memset之类的位置一定要特别注意,memset的复杂度是n不是1


2。

这里写图片描述
这道题一开始的思路就是贪心
因为首先想到在叶节点直接放肯定不优
肯定是能不放,就不放
关键是怎么判断放不放,就是说怎么判断不得不放的时候
如果直接从叶节点往上跳K的距离会出问题
那么就想到记录一个点离他最远的没有覆盖的点的距离
每往上跳一步,距离就++
但是这样在子树合并的时候就会出事,因为一个点放了军队之后,对于上面的点也有覆盖能力
所以再记录一个点往上跳的最大距离,每往上走一步就取最值然后–
记录一个点有没有被覆盖过,为了防止根节点漏判
大概思路就是这样
但是具体实现是有不对的地方
考试时也觉得不对,但是稍微一改就过不了样例,而且自己测了很多组数据都是对的,所以不太敢改,就这么交了
然后就只有70分

正解是什么我不知道
反正我最后A了,调的很不容易,模拟了100的样例,多谢LKQ的图解
发现我的down数组处理的其实很模糊,当时就觉得“差不多好像是这样”就写上去了
心一横就改了状态,只有-1,0,和正数,分别表示这个点被覆盖,不被覆盖,和最远点的距离
明确定义之后就容易多了

完整代码:

#include<stdio.h>#include<algorithm>#include<cstring>using namespace std;const int INF=0x3f3f3f3f;struct edge{    int v,last;}ed[10000010];int n,K,t,num=0,ans=0;int head[5000010],down[5000010],up[5000010],mrk[5000010];void add(int u,int v){    num++;    ed[num].v=v;    ed[num].last=head[u];    head[u]=num;}void dfs(int u,int f){    int tmp1=-INF,tmp2=-INF,tmp3=1,tmp4=0;    for(int i=head[u];i;i=ed[i].last)    {        int v=ed[i].v;        if(v==f) continue ;        dfs(v,u);        tmp3*=(down[v]==-1),tmp4=1;        tmp1=max(tmp1,down[v]);        tmp2=max(tmp2,up[v]);        if(up[v]>0) mrk[u]=1;    }    if(tmp1==-INF) down[u]=0;    else down[u]=tmp1+1;    if(tmp2==-INF) up[u]=0;    else up[u]=tmp2-1;    if(tmp3 && tmp4 && mrk[u]) down[u]=-1;    if(up[u]>=down[u] && up[u]>0 ) down[u]=-1;    if(down[u]>=K) ans++,up[u]=K,down[u]=-1,mrk[u]=1;}int main(){    freopen("general.in","r",stdin);    freopen("general.out","w",stdout);    scanf("%d%d%d",&n,&K,&t);    for(int i=1;i<n;i++)    {        int u,v;        scanf("%d%d",&u,&v);        add(u,v),add(v,u);    }    dfs(1,1);    if(!mrk[1]) ans++;    printf("%d\n",ans);    return 0;}

哎,这样算是死在验证代码上的吧
自己觉得好像还可以就马马虎虎了事
感觉考试时一定要时刻持怀疑态度,稍有不对就要深究,一定要完全明确才行
想到稍有不对的地方要有直接动手实现和模拟的决断
这样


3。

这里写图片描述
这道题考试的时候是真的没有什么思路
只是想了一遍状压的大模拟,暴力
5分钟写完+对拍
确认无误后24分稳稳到手
后面的几个答案小于4的点rand的了8分,总分32

正解略有复杂,但想通了还是很简单
我们把原01串两两异或得到一个新的差分数列
原数列的区间翻转其实就是新数列两点翻转
01翻转考虑成1走到0的位置
不会出现00翻转的情况,因为是白忙活
11翻转考虑成一个1走到另一个1的位置,两个1碰上然后一起消失
两点翻转其实就是一个1走到一个与他相距为bi的地方
可以预处理出来每个1走到每个1最少要走多少步,即翻转多少次
那么问题就变成了一共有K个1,两两之间有一个代价(步数)
两两之间碰到会消失
问最少代价使得所有的1全部消失
K的范围很小,枚举状态就变成一个简单状压DP了
枚举状态枚举想要相遇的两个点,刷表法更新答案
O(k^2 * 2^k)

完整代码:

#include<stdio.h>#include<map>#include<cstring>#include<queue>#include<algorithm>using namespace std;const int INF=0x3f3f3f3f;pair <int,int> p[30];queue <int> state;int n,m,K,cnt=0;int dis[20][100010],a[100010],f[100010],b[100010];void sov1(){       int ste=(1<<n)-1,ans=ste;    for(int i=1;i<=K;i++)    {        int tmp;        scanf("%d",&tmp);        ans^=(1<<tmp-1);    }    for(int i=1;i<=m;i++) scanf("%d",&b[i]);    memset(f,0x3f3f3f3f,sizeof(f));    f[ste]=0;    for(int i=ste;i>=0;i--)        for(int j=1;j<=m;j++)            for(int k=0;k<n-b[j]+1;k++)            {                int tmp=i^(((1<<b[j])-1)<<k);                f[tmp]=min(f[tmp],f[i]+1);            }    printf("%d\n",f[ans]);}void SPFA(pair <int,int> S){    int x0=S.first,y0=S.second;    memset(dis[x0],INF,sizeof(dis[x0]));    while(!state.empty()) state.pop();    state.push(y0),dis[x0][y0]=0;    while(!state.empty())    {        int u=state.front();        state.pop();        for(int i=1;i<=m;i++)        {            if(u+b[i]<=n && dis[x0][u+b[i]]>dis[x0][u]+1)            {                dis[x0][u+b[i]]=dis[x0][u]+1;                state.push(u+b[i]);            }            if(u-b[i]>=0 && dis[x0][u-b[i]]>dis[x0][u]+1)            {                dis[x0][u-b[i]]=dis[x0][u]+1;                state.push(u-b[i]);            }        }    }}int sov(int ste){    f[ste]=0;    for(int i=ste;i>=0;i--)        for(int j=0;j<cnt;j++)        if(i&(1<<j))            for(int k=0;k<cnt;k++)                if(j!=k && i&(1<<k))                     f[i^(1<<j)^(1<<k)]=min(f[i^(1<<j)^(1<<k)],f[i]+dis[j][p[k].second]);    return f[0];}void sov2(){    for(int i=1;i<=K;i++)    {        int tmp;        scanf("%d",&tmp);        a[tmp]=1;    }    for(int i=1;i<=m;i++)        scanf("%d",&b[i]);    for(int i=0;i<=n;i++)        if(a[i] ^ a[i+1]) p[cnt]=make_pair(cnt,i),cnt++;    for(int i=0;i<cnt;i++)        SPFA(p[i]);    memset(f,INF,sizeof(f));    int ans=sov((1<<cnt)-1);    printf("%d\n",ans);}int main(){    freopen("starlit.in","r",stdin);    freopen("starlit.out","w",stdout);    scanf("%d%d%d",&n,&K,&m);    if(n<=16) sov1();    else sov2();    return 0;}

这道题算是提供了一步区间转化为单点的差分思路
先看题目数据范围考虑对什么东西状压,然后考虑转化思路


今天的考试实在是很迷
要不是第一题脑残加了个memset,第二题写的时候脑子不清楚(。。。)不会这么惨
本来是信心题考成这样,我还是太弱啊
叶正好借这几次模拟考试查出自己易犯的错误然后改正,调整自己考试的状态,深化考试的套路
明天休闲字符串考试,要翻盘才好啊

嗯,就是这样

原创粉丝点击