NOIP-DAY-4总结

T1-mine:

题意

有一个 1 维的扫雷游戏,每个格子用表示有雷,用 0/1/2 表示无雷并且相邻格子中有 0/1/2 个雷。给定一个仅包含?、0、1、2 的字符串 s,问有多少种方法将所有的?改为*/0/1/2 使其合法。

题解:

  我们观察可能出现在这个扫雷序列的四种可能符号,发现当序列的值为0或者2时左右两边的序列形式是可以确定,当为1时左右两个点会有两种形态,并且1的这个点的下一个点的形式可以通过1前一个点推导过来,

  发现有比较显然的可以动态规划的性质,于是考虑建立dp的式子,考虑到如果现在这个点如果为‘?’那么这个点的形式由前一个点,前前一个点决定,并且这个点是否是雷会影响到下下个点的形态,所以我们考虑以

  以上三种情态构造dp的式子,有dp[i][0]表示当前点不是雷并且这个点的前一个点不是雷,dp[i][1]表示当前点不是雷并且前一个点是雷,f[i]表示当前点是雷的构造方案数,以上三种情况便包含了所有的情况数。考虑如何转移

  if(s[i]=='*'){dp[i+1][1]=f[i],f[i+1]=f[i];}


        if(s[i]=='0')dp[i+1][0]=dp[i][0];


        if(s[i]=='1')dp[i+1][0]=dp[i][1];f[i+1]=dp[i][0];如果当前点是1那么下一点为雷的构造方案则为当前点不为雷并且前一位为雷的方案数,其他的式子的意义可以以此类推


        if(s[i]=='2'){f[i+1]=dp[i][1];}


        if(s[i]=='?')dp[i+1][0]=(dp[i][1]+dp[i][0])%mod;dp[i+1][1]=f[i]; f[i+1]=(dp[i][0]+dp[i][1]+f[i])%mod;

 

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int mod=1e9+7;
const int maxn=1e6+5;
ll dp[maxn][2];
ll f[maxn];
char s[maxn];
int main()
{
    freopen("mine.in","r",stdin);
    freopen("mine.out","w",stdout);
    scanf("%s",s+1);
    int len=strlen(s+1);
    if(s[1]=='*')dp[2][1]++;
    if(s[1]=='0')dp[2][0]++;
    if(s[1]=='1')f[2]++;
    if(s[1]=='?')f[2]+=2,dp[2][1]++,dp[2][0]++;
    for(int i=2;i<=len;i++)
    {
        if(s[i]=='*'){dp[i+1][1]=f[i],f[i+1]=f[i];}
        if(s[i]=='0')dp[i+1][0]=dp[i][0];
        if(s[i]=='1')
        {
            dp[i+1][0]=dp[i][1];
            f[i+1]=dp[i][0];
        }
        if(s[i]=='2'){f[i+1]=dp[i][1];}
        if(s[i]=='?')
        {
            dp[i+1][0]=(dp[i][1]+dp[i][0])%mod;
            dp[i+1][1]=f[i];
            f[i+1]=(dp[i][0]+dp[i][1]+f[i])%mod;
        }
    }
    printf("%lld\n",(dp[len+1][1]+dp[len+1][0])%mod);
    return 0;
}

T2-water

题目描述

有一块矩形土地被划分成n×m

个正方形小块。这些小块高低不平,每一小块都有自己的高度。水流可以由任意一块地流向周围四个方向的四块地中,但是不能直接流入对角相连的小块中。

一场大雨后,由于地势高低不同,许多地方都积存了不少降水。

给定每个小块的高度,求每个小块的积水高度。

注意:假设矩形地外围无限大且高度为0;

0

题解:

  洛谷上有原题p5930,但是答案的输出与本题不同需要稍微注意一下;

  首先我们发现由于格子的边缘高度为0所以边缘除负数外无法存储水,但是负数也可以靠这个性质先统一填平为0,然后我们便可以从边界去寻找能存储水的地方,发现一个地方能存储水当且仅当这个地方周围是全部比它高

  也就是木桶原理,所以我们考虑用小根堆去存储这些边界,并且BFS向中间扩展,遇到比它小的就灌水,遇到比它高的就入堆表示该点不可以由现有的点更新,而且小根堆所具有的优势就是从小进行枚举保证所有的数只需要被

  被更新且进入堆一次。

#include<bits/stdc++.h>
using namespace std;
const int maxn=505;
int n,m;
struct hp
{
    friend bool operator <(hp a,hp b)
    {
        return a.h>b.h;
    }
    int x,y,h;
};
int xx[4]={0,1,-1,0};
int yy[4]={1,0,0,-1};
int h[maxn][maxn];
int vis[maxn][maxn];
int ans[maxn][maxn];
priority_queue<hp> que;
queue<hp>q;
void bfs(hp a)
{
    q.push(a);
    while(q.size())
    {
        hp u=q.front();
        q.pop();
        if(ans[u.x][u.y]!=-1)continue;
        ans[u.x][u.y]=a.h;
        for(int i=0;i<4;i++)
        {
            hp tmp;
            tmp.x=u.x+xx[i];tmp.y=u.y+yy[i];
            tmp.h=h[tmp.x][tmp.y];
            if(tmp.x>=1&&tmp.x<=n&&tmp.y>=1&&tmp.y<=m&&ans[tmp.x][tmp.y]==-1)
            {
                if(h[tmp.x][tmp.y]<=a.h)q.push(tmp);//找到周围的可以蓄水的更低的点将最低的点并且更新它们蓄水的最高的高度
                else if(!vis[tmp.x][tmp.y]&&h[tmp.x][tmp.y]>u.h)que.push(tmp);
            }
        }
    }
}//一个地方的水的最高的
int main()
{
    freopen("water.in","r",stdin);
    freopen("water.out","w",stdout);
    scanf("%d %d",&n,&m);
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=m;j++)
        {
            scanf("%d",&h[i][j]);
            ans[i][j]=-1;
            if(i==1||i==n||j==1||j==m)//边界是无法储存水的.先将边界推入小根堆
            {
                if(h[i][j]<0)ans[i][j]=-h[i][j],h[i][j]=0;
                hp tmp;
                tmp.x=i;
                tmp.y=j;
                tmp.h=h[i][j];
                que.push(tmp);//小根堆是因为木桶原理,如果用大根堆需要重复更新
                vis[i][j]=1;//表示一个数已经进过了堆中
            }
        }
    }
    while(que.size())
    {
        hp u=que.top();
        que.pop();
        if(ans[u.x][u.y]!=-1)continue;//保证被更新的一定是最小的
        bfs(u);
    }
    int tot=0;
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=m;j++)
        {
            //tot+=ans[i][j]-h[i][j];
            printf("%d ",ans[i][j]-h[i][j]);
        }
        cout<<endl;
     }
     //printf("%d\n",tot);//是洛谷p5930的答案输出方式
    return 0;
}

T3-gcd

有 n个正整数 个正整数 x1~xn,初始时状态均为未选 。
有 m个操作 每个操作给定一编号i,将 xi 的选取状态反 。每次操作后 你需要求出选取的数中有多少个互质的无序数对 。

 

题解

  思路就是对于每一个数ai,计算与它不互素的数的个数再相减

 

  具体来说,对于ai=Πpi​的ki次方我们用集合Aj来表示其因子中刚好只有k个互不相同质因子的集合,即Aj={x=Π1jpi∣(x∣ai)},

  根据容斥原理,最终答案即为∑∣Ai∣∗(−1)i−1\sum|A_i|*(-1)^{i-1}(选自洛谷CF547C Mike and Foam 题解)

 

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll maxn=5e5+5;
bool vis[maxn];
vector<ll>b[200010];
ll s[maxn],d[maxn],n,q,a[maxn];
int main()
{
    //freopen("gcd.in","r",stdin);
    //freopen("gcd.out","w",stdout);
    for(ll i=2;i<maxn;++i)
    {
        ll x=i;s[i]=-1;
        for(ll j=2; j*j<=x;++j)
            if(x % j == 0)
            {
                x/=j;s[i]=-s[i];
                if(x%j==0){s[i]=0;x=1;break;}
            }
        if(x!=1)s[i]*=-1;
    }
    scanf("%lld%lld",&n,&q);
    for(ll i=1;i<=n;++ i)
    {
        scanf("%lld",&a[i]);
        for(ll j=1;j*j<=a[i];++j)
            if(a[i]%j==0)
            {
                if(s[j]!=0)b[i].push_back(j);
                if(a[i]!=j*j&&s[a[i]/j]!=0)b[i].push_back(a[i]/j);
            }
    }
    ll ans=0,tot=0;
    while(q--)
    {
        ll x;
        scanf("%lld", &x);
        if(a[x]==1)
        {
            if(vis[x])ans-=tot-1,-- tot;
            else ans+=tot,++tot;
            vis[x]^=1;
            printf("%lld\n", ans);
            continue;
        }
        if(vis[x])
        {
            vis[x]=0;
            ll t=0;
            for(ll i=0;i<b[x].size();++i)
                t+=s[b[x][i]]*d[b[x][i]],d[b[x][i]]--;
            ans-=tot-t;
            tot--;
        }
        else
        {
            vis[x]=1;
            ll t=0;
            for(ll i=0;i<b[x].size();++i)
            {
                t+=s[b[x][i]]*d[b[x][i]];
                d[b[x][i]]++;
            }    
            ans+=tot-t;
            tot ++;
        }
        printf("%lld\n", ans);
    }
    return 0;
}


posted @ 2021-09-23 17:07  -白翎-windrise  阅读(61)  评论(0)    收藏  举报