[NOIP]模拟17 题解

A.入阵曲

部分分很肥,正解写得常数稍大就会和暴力一个分,考试的时候写什么自己考虑。(滑稽

部分分的循环边界手抖写错了-25 (原本暴力分中的10分都没了啊啊啊)

没写挂的话应该有75,其实就是二维前缀和+暴力枚举点对统计+$a[i][j]$都相等时只枚举子矩形大小再乘上这种大小出现的次数。

正解:$(sum[r]-sum[l-1])\% K=0 ightarrow sum[r]\% K=sum[l-1]\% K$

枚举行数$i,j$和列数$k$,维护i行和j行之间、k列左侧在%K意义下同余矩阵的个数,用桶来实现。

#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
typedef long long ll;
const int N=650;
int n,m;
ll K,ans;
int a[N][N];
ll sum[N][N],sum1[N],cnt[1000005];
int read()
{
    int x=0,f=1;char ch=getchar();
    while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
    while(isdigit(ch))x=x*10+ch-'0',ch=getchar();
    return x*f;
}

int main()
{
    n=read();m=read();K=1LL*read();
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            a[i][j]=read();
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            sum[i][j]=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1]+1LL*a[i][j];
    for(int i=0;i<n;i++)
        for(int j=i+1;j<=n;j++)
        {
            cnt[0]=1;
            for(int k=1;k<=m;k++)
            {
                sum1[k]=(sum[j][k]-sum[i][k]+K)%K;
                ans+=cnt[sum1[k]];
                cnt[sum1[k]]++;
            }
            for(int k=1;k<=m;k++)cnt[sum1[k]]=0;
        }
    cout<<ans<<endl;
    return 0;
}
View Code

B.将军令

我考场都能想到的sb贪心。每次取出深度最大且未被覆盖的点,在它的K级祖先上驻扎即可。前者用堆维护,后者直接暴力修改覆盖状态,注意向上修改时不要只遍历和它在一条链上的。

正确性?读者自证不难。(逃

#include<cstdio>
#include<iostream>
#include<cstring>
#include<queue>
#define pa pair<int,int>
#define re register
using namespace std;
int read()
{
    int x=0,f=1;char ch=getchar();
    while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
    while(isdigit(ch))x=x*10+ch-'0',ch=getchar();
    return x*f;
}
const int N=100005;
int n,K,t;
int to[N<<1],nxt[N<<1],head[N],tot;
inline void add(int x,int y)
{
    to[++tot]=y;
    nxt[tot]=head[x];
    head[x]=tot;
}
int dep[N],fa[N];
priority_queue<pa> q;
void dfs1(int x,int deep)
{
    dep[x]=deep;
    for(re int i=head[x];i;i=nxt[i])
    {
        int y=to[i];
        if(!dep[y])dfs1(y,deep+1),fa[y]=x;
    }
    return ;
}
int cover[N],anc,ans;
void getan(int x,int rest)
{
    if(!rest||x==1)
    {
        anc=x;
        return ;
    }
    getan(fa[x],rest-1);
    return ;
}
void dfs2(int x,int rest,int f)
{
    cover[x]=1;
    if(!rest)return ;
    for(re int i=head[x];i;i=nxt[i])
    {
        int y=to[i];
        if(y==f)continue;
        dfs2(y,rest-1,x);
    }
    return ;
}

int main()
{
    n=read();K=read();t=read();
    for(re int i=1;i<n;i++)
    {
        int x=read(),y=read();
        add(x,y);add(y,x);
    }
    dfs1(1,1);
    for(re int i=1;i<=n;i++)
        q.push(make_pair(dep[i],i));
    while(!q.empty())
    {
        int x=q.top().second;
        q.pop();
        if(cover[x])continue;
        getan(x,K);
        //cout<<anc<<endl;
        dfs2(anc,K,-1);
        ans++;
    }
    cout<<ans<<endl;
    return 0;
}
View Code

C.星空

区间状态反转可以看作区间异或1,但区间操作不好处理,考虑通过差分转化为单点操作。即把对原数组上$[L,R]$区间的操作转化为差分数组上$L-1$和$R$两个点的操作。这里用到了异或差分:$dif[i]=a[i] xor a[i+1]$

所以问题变成了:有一个01串,每次对其中两个点$xor 1$,需要多少次把这个串的每一位都变成0。(差分数组全0对应原数组全1)

可以发现,消去两个1的费用与他们之间的距离有关。bfs预处理出来后,问题再次得到转化:每次取出一对物品,每对物品取出都有一定代价,如何取出使得代价最小。由于不亮的灯泡很少,这个问题完全可以状压解决。

#include<cstdio>
#include<iostream>
#include<cstring>
#include<queue>
#include<vector>
using namespace std;
int read()
{
    int x=0,f=1;char ch=getchar();
    while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
    while(isdigit(ch))x=x*10+ch-'0',ch=getchar();
    return x*f;
}
const int N=40005;
int n,m,K;
int a[N],op[N],dif[N];
int pos[N],sz;
int vis[N],dis[20][20],d[N];
int find0[N<<1],dp[N<<1];
void bfs(int node)
{
    memset(d,0,sizeof(d));
    memset(vis,0,sizeof(vis));
    queue<int> q;
    q.push(node);
    vis[node]=1;d[node]=0;
    while(!q.empty())
    {
        int x=q.front();
        q.pop();
        for(int i=1;i<=m;i++)
        {
            int s1=x+op[i],s2=x-op[i];
            if(s1<=n&&!vis[s1])
            {
                q.push(s1);
                d[s1]=d[x]+1;
                vis[s1]=1;
            }
            if(s2>=0&&!vis[s2])
            {
                q.push(s2);
                d[s2]=d[x]+1;
                vis[s2]=1;
            }
        }
    }
}

int main()
{
    n=read();K=read();m=read();
    for(int i=1;i<=K;i++)
        a[read()]=1;
    for(int i=1;i<=m;i++)
        op[i]=read();
    for(int i=0;i<=n;i++)
    {
        dif[i]=a[i]^a[i+1];
        if(dif[i])pos[++sz]=i;
    }
    for(int i=1;i<=sz;i++)
    {
        bfs(pos[i]);
        for(int j=1;j<=sz;j++)
            dis[i][j]=d[pos[j]];
    }
    for(int s=0;s<(1<<sz);s++)
    {
        find0[s]=sz-1;
        for(int i=0;i<sz;i++)
            if(((s>>i)&1)==0)
            {
                find0[s]=i;
                break;
            }
    }
    memset(dp,0x3f,sizeof(dp));
    dp[0]=0;
    for(int s=0;s<(1<<sz);s++)
        for(int i=find0[s]+1;i<sz;i++)
        {
            if(((s>>i)&1)==0&&dis[find0[s]+1][i+1])
            {
                int now=(s|(1<<find0[s])|(1<<i));
                dp[now]=min(dp[now],dp[s]+dis[find0[s]+1][i+1]);
                //cout<<dp[now]<<endl;
            }
        }
    cout<<dp[(1<<sz)-1]<<endl;
    return 0;
}
View Code
原文地址:https://www.cnblogs.com/Rorschach-XR/p/11342841.html