其他分享
首页 > 其他分享> > 6.22 NOI 模拟

6.22 NOI 模拟

作者:互联网

\(T1\)递归

给出 \(Thue-Morse\) 序列的定义三

每次 \(0\rightarrow 01\),\(1\rightarrow 10\)

\(0\rightarrow 01 \rightarrow 0110 \rightarrow 01101001\rightarrow...\)

我们现在已知串 \(010010\) 考虑将其划分

\(0\ 10\ 01\ 0\) 或者 \(01\ 00 \ 10\)

显然第二个是不合法的。

我们把第一个补全,为 \(10\ 10\ 01\ 01\)

然后合并一下为 \(1100\) ,我们假设 \(1100\) 出现的位置是 \(i\) 我们 \(010010\) 出现的位置为 \(2i+1\)

然后我们得到递推式 \(f(l,r)=f(\lfloor l/2\rfloor,\lfloor r/2\rfloor)+(l\mod 2)\)

我们只需要暴力求小数据即可

#include<bits/stdc++.h>
using namespace std;
int f[3][8]={{0,1},{5,2,0,1},{0,4,3,1,5,2}};
long long slo(long long l,long long r)
{
    if(r-l+1>=4) return 2*slo(l/2,r/2)+(l&1);
    long long S=0;
    for(long long i=l;i<=r;i++)
    {
        S|=(__builtin_popcountll(i)&1)<<(i-l);
    }
    return f[r-l][S];
}
int main()
{
    int q;
    scanf("%d",&q);
    while(q--)
    {
        long long l,r;
        scanf("%lld%lld",&l,&r);
        cout<<slo(l,r)<<"\n";
    }
}

$T2\ $加边

按照原图跑以 \(1\) 为根的 \(bfs\) 树

\(b>2\times a\)答案是 \(dep\times a\)

否则对于 \(b\) 边进行 \(bfs\) ,类似三元环进行删边,复杂度可以保证在 \(O(m\sqrt m)\)

#define Eternal_Battle ZXK
#include<bits/stdc++.h>
#define int long long
#define MAXN 200005
using namespace std;
#define FastIO
#ifdef FastIO
    char buf[1<<21],*p1,*p2;
    #define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
#endif
template<class T>
T Read()
{
    T x=0,f=1;
    char ch=getchar();
    while(ch<'0'||ch>'9')
    {
        if(ch=='-')
        f=-1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9')
    {
        x=(x<<1)+(x<<3)+(ch^'0');
        ch=getchar();
    }
    return x*f;
}
int (*read)()=Read<int>;
#define read Read<int>
vector<int>rd[MAXN],gd[MAXN];
int dis[MAXN],dep[MAXN],n,m,a,b;
void add(int u,int v)
{
    rd[u].push_back(v);
    gd[u].push_back(v);
}
void bfs()
{
    queue<int>q;
    memset(dep,-1,sizeof(dep));
    q.push(1);
    dep[1]=0;
    while(q.size())
    {
        int now=q.front();
        q.pop();
        for(int i=0;i<rd[now].size();i++)
        {
            int y=rd[now][i];
            if(dep[y]!=-1) continue;
            q.push(y); dep[y]=dep[now]+1;
        }
    }
}
bitset<MAXN>Min;
void bfs_dis()
{
    queue<int>q;
    memset(dis,-1,sizeof(dis));
    q.push(1);
    dis[1]=0;
    while(q.size())
    {
        int now=q.front();
        q.pop();
//      Min.reset();
        for(vector<int>::iterator it=rd[now].begin();it!=rd[now].end();)
        {
            int y=*it; 
            Min[y]=1;
            it++;
        }
        for(vector<int>::iterator it=rd[now].begin();it!=rd[now].end();)
        {
            int y=*it;
            if(dis[y]!=-1) it=rd[now].erase(it);
            else it++;
            for(vector<int>::iterator it1=gd[y].begin();it1!=gd[y].end();)
            {
                int ty=*it1; 
                if(dis[ty]!=-1)
                {
                	it1=gd[y].erase(it1);
				}
				else
			    {
			    	it1++;
					if(Min[ty]) continue;
	                dis[ty]=dis[now]+b;
	                q.push(ty);
				}
            }
        }
        for(int i=0;i<rd[now].size();i++)
        {
            int y=rd[now][i];
            Min[y]=0;
        }
    }
}
signed main()
{
    scanf("%lld%lld%lld%lld",&n,&m,&a,&b);
    for(int i=1,u,v;i<=m;i++)
    {
        u=read();v=read();
        add(u,v); add(v,u);
    }
    bfs();
    if(b>=2*a)
    {
       for(int i=2;i<=n;i++)
       {
           cout<<dep[i]*a<<"\n";
       }
    }
    else
    {
        bfs_dis();
        for(int i=2;i<=n;i++)
        {
            if(dep[i]%2==0)
            {
               cout<<(dep[i]/2)*b<<"\n";
            }
            else
            {
               if(dis[i]==-1) cout<<dep[i]/2*b+a<<"\n";
               else cout<<min(dep[i]/2*b+a,dis[i])<<"\n";
            }
        }
    }
}

$T3\ $虐场

考虑枚举 \(k\),考虑已知 \(k\) 之后应该怎么求解

设 \(c_i=b_{j+1}-b_{j}-k\)表示空场的和

我们先选定连续 \(n\) 场,向左右移动,可以导致 \(b\) 整体加减 \(1\),\(c\) 不变

首先最大化收益,先考虑选最右侧 \(n\times k\) 场,然后往左移动

设 \(d_i=b_i-a_i\) 我们要 $d_i\leq 0 $,并且 \(\sum |b_i|\) 尽可能小

每次贪心的话,就把目前后缀最大值位置向左平移,最后每个位置移动的位置是后缀的最大值

设后缀最大值 \(suf_i\),答案是 \(\sum (suf_i-b_i)\)

至于移动限制考虑我们只能进行 \(m-k\times n\) 次前缀减,后面的只能进行整体减就好了

发现答案是关于 \(k\) 的凸函数,可以三分找极值点

#define Eternal_Battle ZXK
#include<bits/stdc++.h>
#define int long long
#define MAXN 200010
using namespace std;
int n,m,k,lim,a[MAXN],b[MAXN];
int check(int x)
{
    long long nw=0;
    for(int i=1;i<=n;i++)
    {
        b[i]=m*x-x*(x+1)/2*n+i*x-a[i];
        nw=max(nw,b[i]);
    }
    long long an=0,sum=max(nw-m+x*n,0ll);
    for(int i=n;i>=1;i--)
    {
        sum=max(sum,b[i]);
        an+=sum-b[i];
    }
    return an;
}
signed main()
{
    scanf("%lld%lld",&n,&m);
    k=lim=m/n;
    for(int i=1;i<=n;i++)
    {
        scanf("%lld",&a[i]);
        int l=1,r=m/n;
        while(l<=r)
        {
            long long mid=(l+r)>>1;
            if(mid*(mid-1)/2*n+i*mid<=a[i]) l=mid+1;
            else r=mid-1;
        }
        lim=min(lim,r);
        l=1,r=lim;
        while(l<=r)
        {
            long long mid=(l+r)>>1;
            if(m*mid-mid*(mid+1)/2*n+i*mid-a[i]-m+mid*n<=0) l=mid+1;
            else r=mid-1;
        }
        k=min(l,k);
    }
    int ans=check(min(lim,k));
    int l=0,r=k-1;
    while(r-l>10)
    {
        int mid=(l+r)>>1;
        long long an1=check(mid),an2=check(mid+1);
        if(an1<an2) r=mid-1;
        else l=mid+2;
    }
    for(int i=l;i<=r;i++)
    {
        ans=min(ans,check(i));
    }
    ans=-ans;
    for(int i=1;i<=n;i++)
    {
        ans+=a[i];
    }
    cout<<ans<<endl;
}

标签:NOI,int,mid,long,define,6.22,now,模拟,dis
来源: https://www.cnblogs.com/Eternal-Battle/p/EB_is_cute.html