[NOIP模拟26]题解

今天的考试题改自闭了……所以滚来写陈年题解。

A.*****贪婪*****

RT,出题人告诉我们这题要贪心。

最优的策略一定是拖到必须断的时候再断开(虽然并不知道为什么)。

如果一段序列满足题目中的性质,那么一定有$gcd(a_i-a_{i-1},a_{i+1}-a_i,...)$不为1且$a_i,a_{i+1},...$各不相同。所以维护每段的相邻两项差值的gcd,遇到不符合或者重复的元素就ans++。set写起来比较方便。

#include<cstdio>
#include<iostream>
#include<cstring>
#include<set>
#include<cmath>
using namespace std;
const int N=100005;
int n,a[N],ans=1;
set<int> b;
int gcd(int x,int y)
{
    if(!y)return x;
    return gcd(y,x%y);
}
int now;
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
        scanf("%d",&a[i]);
    b.insert(a[1]);
    for(int i=2;i<=n;i++)
    {
        now=gcd(now,abs(a[i]-a[i-1]));
        if(now==1||b.count(a[i]))
        {
            ans++;
            now=0;
            b.clear();b.insert(a[i]);
        }
        else b.insert(a[i]);
    }
    cout<<ans<<endl;
    return 0;
}
View Code

B.**见证*******

RT,出题人让我们见证$n^2$过500000。

没有打std中的离线dfs序解法,直接用有向边代表包含关系,每次询问暴力dfs统计答案即可。注意K=1要特判建双向边。

#include<cstdio>
#include<iostream>
#include<cstring>
#include<vector>
using namespace std;
int n,m,cnt;
const int N=500005;//change it!
int head[N],nxt[N],to[N],tot,vis[N];
void add(int x,int y)
{
    to[++tot]=y;
    nxt[tot]=head[x];
    head[x]=tot;
}
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;
}
bool dfs(int x,int f,int aim)
{
    if(x==aim)return 1;

    for(int i=head[x];i;i=nxt[i])
    {
        if(to[i]!=f)
            if(dfs(to[i],x,aim))return 1;
    }
    return 0;
}
int main()
{
    n=read();m=read();cnt=n;
    while(m--)
    {
        int opt=read();
        if(!opt)
        {
            int op=read(),K=read(),now=++cnt;
            if(K==1)
            {
                int tmp=read();
                add(now,tmp);add(tmp,now);
            }
            else if(op==0)
            {
                for(int i=1;i<=K;i++)
                    add(now,read());    
            }
            else if(op==1)
            {
                for(int i=1;i<=K;i++)
                    add(read(),now);
            }
        }
        else if(opt==1)
        {
            int x=read(),y=read();


            printf("%d\n",dfs(x,0,y));
        }
    }    
    return 0;
}
View Code

C.**堆积******

RT,出题人告诉我们要用到堆。

暴力dp的方程可以一眼切掉:$dp[i]=\min \limits _{j=max(0,i-K)}^{i-1} dp[j]+max(sum[i]-sum[j],b[j])$

对于所有j,$dp[j]+b[j]$和$dp[j]-sum[j]$都是定值

所以用两个堆,分别维护二者。要求的$dp[i]=min(heap1.top,heap2.top+sum[i])$

首先检查第一个栈,如果取出的j比i-K小,pop掉继续取。如果取出的比$dp[j]-sum[j]+sum[i]$小,把它加进第二个堆里,pop掉。

就这样取到合法的为止,第二个堆也是一样。

时间复杂度$O(nlogn)$

#include<cstdio>
#include<iostream>
#include<cstring>
#include<queue>
using namespace std;
typedef long long ll;
#define pa pair<ll,int>
int n,K;
const int N=500005;
int a[N],b[N];
ll sum[N],dp[N];
priority_queue<pa,vector<pa>,greater<pa> >q1,q2;
int main()
{
    scanf("%d%d",&n,&K);
    for(int i=1;i<=n;i++)
        scanf("%d",&a[i]),sum[i]=sum[i-1]+1LL*a[i];
    for(int i=0;i<n;i++)
        scanf("%d",&b[i]);
    memset(dp,0x3f,sizeof(dp));
    dp[0]=0;
    q1.push(make_pair(dp[0]+1LL*b[0],0));
    for(int i=1;i<=n;i++)
    {
        ll val1=0x7ffffffffff,val2=0x7ffffffffff;
        while(!q1.empty())
        {
            ll val=q1.top().first;int id=q1.top().second;
            if(id<i-K)
            {
                q1.pop();continue;
            }
            if(val<dp[id]-sum[id]+sum[i])
            {
                q2.push(make_pair(dp[id]-sum[id],id));q1.pop();
                continue;
            }
            val1=min(val1,val);
            break;
            
        }

        while(!q2.empty())
        {
            ll val=q2.top().first;
            int id=q2.top().second;
            if(id<i-K)
            {
                q2.pop();continue;
            }
            val2=min(val2,val);
            break;


        }
        dp[i]=min(val1,val2+sum[i]);
        //cout<<i<<' '<<dp[i]<<endl;
        q1.push(make_pair(dp[i]+b[i],i));
    }
    cout<<dp[n]<<endl;
    return 0;
}
View Code

猜你喜欢

转载自www.cnblogs.com/Rorschach-XR/p/11391241.html