NOIP 2017.10.24 总结+心得

这里写图片描述
世界真的很大
今天的考试厄运缠身,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 2
1 2 1
2 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,第二题写的时候脑子不清楚(。。。)不会这么惨
本来是信心题考成这样,我还是太弱啊
叶正好借这几次模拟考试查出自己易犯的错误然后改正,调整自己考试的状态,深化考试的套路
明天休闲字符串考试,要翻盘才好啊

嗯,就是这样

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值