• 2019-8-11 考试总结


    A. 入阵曲

    直接二维前缀和复杂度$O(n^4)$可得$60$。

    另外的性质分数可得$15$,暴力$75$。

    正解是$O(n^3)$。

    设右下角格子坐标为$(i,j)$,左上角格子为$(p,k)$。

    那么满足条件的必须是$(sum[i][j]-sum[i][k-1]-sum[p-1][j]+sum[p-1][k-1])\% k=0$,

    就是$sum[i][j]-sum[i][k-1]=sum[p-1][j]-sum[p-1][k-1]$,

    然后这个可以用一个桶存余数为$i$的数的个数$S[i]$,

    然后先枚举列,再枚举行,把每一行的$sum[i][j]-sum[i][k-1]$存下来。

    然后就$O(1)$查找,复杂度$O(n^3)$。

    丑陋的代码:

    #include<iostream>
    #include<cstring>
    #include<cstdio>
    #define Reg register
    #define Maxn 450
    using namespace std;
    int n,m,k,W[Maxn][Maxn],sum[Maxn][Maxn],g[1000050];
    long long ans=0;
    int main()
    {
        scanf("%d%d%d",&n,&m,&k);
        for(Reg int i=1;i<=n;++i)
        {
            for(Reg int j=1;j<=m;++j)
            {
                scanf("%d",&W[i][j]);
                sum[i][j]=(sum[i][j-1]+sum[i-1][j]-sum[i-1][j-1]+W[i][j]+k)%k;
            }
        }
        for(Reg int j=1;j<=m;++j)
        {
            for(Reg int p=j;p>=1;--p)
            {
                for(Reg int i=1,x;i<=n;++i)
                {
                    x=(sum[i][j]-sum[i][p-1]+k)%k;
                    ans+=g[x]; if(x==0) ++ans;
                    ++g[x];
                }
                for(Reg int i=1,x;i<=n;++i)
                {
                    x=(sum[i][j]-sum[i][p-1]+k)%k;
                    --g[x];
                }
            }
        }
        printf("%lld",ans);
        return 0;
    }
    View Code

    B. 将军令

    好吧,又是贪心。

    没想到要贪心,想拿$k=2$之前的分,然后$dp$状态都没定对。

    最后就拿了$40$分。

    每次贪心找最深的没有被守的节点的$k$次祖先,然后守他的$k$次祖先。

    这样贪心为什么对的,

    因为如果对于最深的节点,如果不守他的$k$次祖先,那么肯定会守深度更大的一个节点,

    而深度更大的节点肯定不比它更优。

    这可以感性地理解一下。

    不知道能不能严谨地证明。

    丑陋的代码:

    #include<algorithm>
    #include<iostream>
    #include<cstring>
    #include<cstdio>
    #include<vector>
    #define Reg register
    #define Maxn 100050
    #define INF 0x7ffffffff
    #define _min(x,y) ((x)<(y)?(x):(y))
    using namespace std;
    bool vis[Maxn];
    int n,k,t,ans,tot,fir[Maxn],dep[Maxn],num[Maxn],fat[Maxn],vap[Maxn];
    struct Tu {int st,ed,next;} lian[Maxn*2];
    vector<vector<int> > son(Maxn);
    void add(int x,int y)
    {
        lian[++tot].st=x;
        lian[tot].ed=y;
        lian[tot].next=fir[x];
        fir[x]=tot;
        return;
    }
    bool comp(int x,int y) {return dep[x]>dep[y];}
    void dfs(int x)
    {
        vis[x]=1;
        for(Reg int i=fir[x];i;i=lian[i].next)
        {
            if(vis[lian[i].ed]) continue;
            dep[lian[i].ed]=dep[x]+1;
            dfs(lian[i].ed);
            fat[lian[i].ed]=x;
            son[x].push_back(lian[i].ed);
        }
        return;
    }
    void update(int x,int len,int k)
    {
        vis[x]=1;
        if(len==0||vap[x]>len) return;
        vap[x]=len;
        for(Reg int i=0,p;i<son[x].size();++i)
        {
            p=son[x][i];
            if(p!=k) update(p,len-1,0);
        }
        update(fat[x],len-1,x);
        return;
    }
    int main()
    {
    //    freopen("text.in","r",stdin);
        scanf("%d%d%d",&n,&k,&t);
        for(Reg int i=1,x,y;i<=n-1;++i)
        {
            scanf("%d%d",&x,&y);
            add(x,y); add(y,x);
        }
        dep[1]=1; dfs(1);
        for(Reg int i=1;i<=n;++i) num[i]=i;
        sort(num+1,num+n+1,comp);
        memset(vis,0,sizeof(vis));
        for(Reg int i=1;i<=n;++i)
        {
            if(!vis[num[i]])
            {
                int now=num[i];
                for(Reg int j=1;j<=k;++j)
                {
                    if(fat[now]) now=fat[now];
                    else break;
                }
                ++ans;
                update(now,k,0);
            }
        }
        printf("%d",ans);
        return 0;
    }
    View Code

    C. 星空

    正解:

    把亮的灯泡看作$1$,不亮的灯泡看作$0$,

    每次修改会改一个连续的长度为$L$的区间,把区间中的灯泡异或$1$,

    把区间差分,设$b[i]=a[i]^a[i-1]$,并且让$a[0]=1$,

    为了方便,把$b[i]^1$。

    那么现在要做的就是修改左右端点,让整个$b$序列变成$1$。

    修改区间时要修改的是$i$和$i+len$。

    每次修改肯定要修改有$0$的地方,否则肯定不优。

    那么如果修改的两个端点一个是$0$一个是$1$,那么就相当于把$1$变成$0$,把$0$变成$1$。

    如果两个都是$0$,那么这两个$0$都会变成$1$,

    下面是玄学操作:

    把$0->1$看作点的移动,第二种情况也看作点的移动。

    相当于$0$可以移动到$1$的位置,而两个$0$相碰会消失。。。

    接下来要做的就是把所有的$0$都相碰。

    转化问题:移动所有的$0$,让所有的$0$两两相碰。

    接下来可以$bfs$,求出每个$0$到其他$0$的最短距离。

    再转化问题:有一堆操作,每次操作可以让两个$0$消失,代价为$dis[i][j]$,问让所有的$0$消失的最小代价。

    由于$k$非常的小,考虑状压。

    方程很好写。

    丑陋的代码:

    #include<algorithm>
    #include<iostream>
    #include<cstring>
    #include<cstdio>
    #include<queue>
    #define Maxn 400050
    #define Reg register
    #define INF 0x7fffff
    #define _min(x,y) ((x)<(y)?(x):(y))
    using namespace std;
    int n,k,m,cnt,tot,ans,A[Maxn],lss[Maxn],len[Maxn],num[Maxn],dis[250][250];
    int dp[1<<17],bin[1<<18];
    bool vis[250][Maxn];
    int q[Maxn],p[Maxn];
    void bfs(int x)
    {
        queue<int> q,p;
        q.push(x);
        p.push(0);
        while(!q.empty())
        {
            int k=q.front(); q.pop();
            int l=p.front(); p.pop();
            if(num[k]&&k!=x) dis[num[x]][num[k]]=l;
            vis[num[x]][k]=1;
            for(Reg int i=1;i<=m;++i)
            {
                if(k+len[i]<=n+1&&!vis[num[x]][k+len[i]])
                {
                    vis[num[x]][k+len[i]]=1;
                    q.push(k+len[i]);
                    p.push(l+1);
                }
                if(k-len[i]>=1&&!vis[num[x]][k-len[i]])
                {
                    vis[num[x]][k-len[i]]=1;
                    q.push(k-len[i]);
                    p.push(l+1);
                }
            }
        }
        return;
    }
    int main()
    {
    //    freopen("text.in","r",stdin);
    //    freopen("text1.out","w",stdout);
        scanf("%d%d%d",&n,&k,&m);
        A[0]=A[n+1]=1;
        memset(dis,0xf,sizeof(dis));
        for(Reg int i=1;i<=n;++i) A[i]=1;
        for(Reg int i=1,x;i<=k;++i) {scanf("%d",&x); A[x]=0;}
        for(Reg int i=0;i<=n;++i) lss[i]=A[i]^A[i+1]^1;
        for(Reg int i=1;i<=n+1;++i) A[i]=lss[i-1];
        for(Reg int i=1;i<=m;++i) scanf("%d",&len[i]);
        for(Reg int i=1;i<=n+1;++i) if(!A[i]) num[i]=++cnt;
        for(Reg int i=1;i<=n+1;++i) if(num[i]) bfs(i);
        for(Reg int i=0;i<=cnt;++i) bin[1<<i]=i;
        memset(dp,0xf,sizeof(dp));
        dp[(1<<cnt)-1]=0;
        ans=INF;
        for(Reg int i=1;i<=cnt;++i)
        {
            for(Reg int k=0;k<(1<<cnt);++k)
            {
                if((k&(1<<(i-1)))==0) continue;
                for(Reg int l=k;l;l-=(l&-l))
                {
                    int j=bin[(l&-l)]+1;
                    if((l&-l)==0||j==i||j==0) continue;
                    dp[k^(1<<(j-1))^(1<<(i-1))]=_min(dp[k^(1<<(j-1))^(1<<(i-1))],dp[k]+_min(dis[j][i],dis[i][j]));
                }
            }
        }
        printf("%d",dp[0]);
        return 0;
    }
    View Code

    总结:

    翻看之前的考试总结,好像真的是流水帐。。。

    每次考完都感觉很。。没有好好考虑自己的问题。

    前半小时读完$T1$的题,然后码了暴力,感觉基本上能拿到$75$分的好成绩。

    滚去看$T2$,想到两个思路,一个二分,一个树形$dp$,

    二分很快否定,树形$dp$想拿到$k=0,k=1,k=2$的分数。

    好像打了不到$2$个小时,手摸样例基本都对。

    然后胆战心惊地跳过了这道题,以为自己能拿到$75+60+$的成绩。

    $T3$没想拿多少分,因为时间不是很多,之后还要再想$T1$正解。

    最近几套题部分分给的很多。

    最后$65+40+12=117$。

    考场上想不到非常正确的思路,$T2$的$dp$状态都定义错了。。。

    最近考试的贪心出现很频繁,如果想到整个题很简单,否则非常麻烦。

    $T1$好好想应该能够想出来,不能很快放弃。

    好好反思。

  • 相关阅读:
    IE无法打开internet网站已终止操作的解决的方法
    iOS IAP教程
    splice()函数的使用方法
    socket通信简单介绍
    puppet安装和使用
    解惑:NFC手机怎样轻松读取银行卡信息?
    深入浅出谈开窗函数(一)
    TCP/IP协议,HTTP协议
    动态规划0—1背包问题
    栈的链式存储结构及其基本运算的实现
  • 原文地址:https://www.cnblogs.com/Milk-Feng/p/11336506.html
Copyright © 2020-2023  润新知