【LGR-051】洛谷9月月赛

【LGR-051】洛谷9月月赛

luogu

签到题

description

给出\(K\)和质数\(m\),求最小的\(N\)使得\(111....1\)\(N\)\(1\)\(\equiv k \mod m\)

\(m\le10^{11},0 \le k < m\)

solution

\(N\)\(1\)写成等比数列求和的形式,不难推出这个式子:\(10^N\equiv9k+1\mod m\)

所以直接上\(BSGS\)就行了。

应该是不存在无解的情况的,毕竟无解很容易构造而题目中并没有说无解怎么处理。

code

#include<cstdio>
#include<algorithm>
#include<cmath>
#include<map>
using namespace std;
#define ll long long
ll k,m,sz;map<ll,ll>M;
ll mul(ll a,ll b){
    ll res=0;
    while (b) {if (b&1) res=(res+a)%m;a=(a+a)%m;b>>=1;}
    return res;
}
ll fastpow(ll a,ll b){
    ll res=1;
    while (b) {if (b&1) res=mul(res,a);a=mul(a,a);b>>=1;}
    return res;
}
int main(){
    scanf("%lld%lld",&k,&m);sz=sqrt(m)+1;
    for (ll i=0,t=(9*k+1)%m;i<sz;++i,t=mul(t,10%m)) M[t]=i;
    for (ll i=1,tt=fastpow(10%m,sz),t=tt;i<=sz;++i,t=mul(t,tt))
        if (M.count(t)) return printf("%lld\n",i*sz-M[t]),0;
}

灭顶之灾

description

有一张\(n\times m\)的表格,在第一行任意选取一个位置填上\(1\),然后按照从左往右、从上往下的顺序依次将表格填满(第一行\(1\)前面的位置上是空的)。给出\(q\)组限制,每个形如\(a_i\)\(b_i\)之间的所有数会被填在同一行。问满足所有限制的前提下第一行选取位置方法是否存在,是否唯一。

\(n,m\le10^{18},1\le a_i \le b_i \le n\times m,0 \le b_i-a_i <m,q\le 5\times10^5\)

solution

假设\(1\)填在第一行第一列,求出\(a_i,b_i\)填的原始位置\(L,R\)\(1\)的位置每往右移动一位,相当于给所有数加上一个相同的增量\(x\)

如果\(L\le R\),那么\(x\in[0,m-R]\)\(x\in[m-L+1,m-1]\),否则\(x\in[m-L+1,m-R]\)

如果\(m\)比较小的话可以直接差分,但现在\(m\le10^{18}\),所以就把差分标记存下来然后扫描线。记得空间要开四倍。

code

#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
#define ll long long
ll gi(){
    ll x=0,w=1;char ch=getchar();
    while ((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
    if (ch=='-') w=0,ch=getchar();
    while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
    return w?x:-x;
}
ll n,m,ans,tot,Ans;int s,q,cnt;
struct node{
    ll x;int y;
    bool operator < (const node &b)
        {return x<b.x;}
}a[2000005];
void cover(ll l,ll r){
    if (l>r) return;
    a[++cnt]=(node){l,1};
    a[++cnt]=(node){r+1,-1};
}
int main(){
    n=gi();m=gi();s=gi();q=gi();
    for (int i=1;i<=s;++i){
        ll l=gi()%m,r=gi()%m;
        if (!l) l=m;if (!r) r=m;
        if (l<=r) cover(0,m-r),cover(m+1-l,m-1);
        else cover(m+1-l,m-r);
    }
    sort(a+1,a+cnt+1);a[++cnt]=(node){m,0};
    for (int i=1,j=0;i<=cnt;++i){
        if (a[i].x!=a[i-1].x)
            if (j==s) ans=a[i-1].x,tot+=a[i].x-a[i-1].x;
        j+=a[i].y;
    }
    if (!tot) return puts("Impossible!"),0;
    if (tot>1) return puts("Uncertain!"),0;
    while (q--){
        ll val=gi()+ans,x,y;
        if ((val+m-1)/m>n) x=y=0;
        else x=(val+m-1)/m,y=val%m?val%m:m;
        Ans^=x^y;
    }
    printf("%lld\n",Ans);return 0;
}

快递员

description

一棵带权树,有\(m\)组询问\(a_i,b_i\)。你需要在树上选一个点\(x\),最小化\(\max_{i=1}^m\{dist(x,a_i)+dist(x,b_i)\}\)

\(n \le 10^5\)

solution

先随便选一个点,对每组询问计算答案。找出那些答案最大的询问,如果所有满足使答案最大的询问都在当前点的同一个子树内,那么把选取的点往这个子树内移动就有可能使答案更优(注意是有可能,你依然需要对每次计算的答案取\(\min\)),那么就往这个子树里走。

优化一下,往子树的重心走,复杂度就是\(O(n\log n)\)

code

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<vector>
using namespace std;
int gi(){
    int x=0,w=1;char ch=getchar();
    while ((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
    if (ch=='-') w=0,ch=getchar();
    while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
    return w?x:-x;
}
#define ll long long
const int N = 2e5+5;
int n,m,to[N],nxt[N],ww[N],head[N],cnt,root,sum,RT;
int fa[N],dep[N],sz[N],son[N],top[N],w[N],vis[N],a[N],b[N];
ll dis[N],ans=1ll<<60;vector<pair<int,int> >E[N];
void link(int u,int v,int w){
    to[++cnt]=v;nxt[cnt]=head[u];ww[cnt]=w;head[u]=cnt;
}
void dfs1(int u,int f){
    fa[u]=f;dep[u]=dep[f]+1;sz[u]=1;son[u]=0;
    for (int e=head[u];e;e=nxt[e])
        if (to[e]!=f){
            dis[to[e]]=dis[u]+ww[e];
            dfs1(to[e],u);sz[u]+=sz[to[e]];
            if (sz[to[e]]>sz[son[u]]) son[u]=to[e];
        }
}
void dfs2(int u,int f){
    top[u]=f;if (son[u]) dfs2(son[u],f);
    for (int e=head[u];e;e=nxt[e])
        if (to[e]!=fa[u]&&to[e]!=son[u])
            dfs2(to[e],to[e]);
}
void getroot(int u,int f){
    sz[u]=1;w[u]=0;
    for (int e=head[u];e;e=nxt[e])
        if (to[e]!=f&&!vis[to[e]]){
            getroot(to[e],u);sz[u]+=sz[to[e]];
            w[u]=max(w[u],sz[to[e]]);
        }
    w[u]=max(w[u],sum-sz[u]);
    if (w[u]<w[root]) root=u;
}
void solve(int u,int f){
    vis[u]=1;
    for (int e=head[u];e;e=nxt[e])
        if (!vis[to[e]]){
            getroot(to[e],0),sum=sz[to[e]],root=0;
            getroot(to[e],0);E[u].push_back(make_pair(to[e],root));
            solve(root,u);
        }
}
int jump(int u,int v){
    if (u==v) return v;int res=0;
    while (top[u]^top[v]) res=top[u],u=fa[top[u]];
    return u==v?res:son[v];
}
void work(int u){
    ll mx=0;int pos=0;
    dis[u]=0;dfs1(u,0);dfs2(u,u);
    for (int i=1;i<=m;++i) mx=max(mx,dis[a[i]]+dis[b[i]]);
    ans=min(ans,mx);
    for (int i=1;i<=m;++i)
        if (dis[a[i]]+dis[b[i]]==mx){
            int x=jump(a[i],u),y=jump(b[i],u);
            if (x!=u){
                if (pos&&pos!=x) printf("%lld\n",ans),exit(0);
                pos=x;
            }
            if (y!=u){
                if (pos&&pos!=y) printf("%lld\n",ans),exit(0);
                pos=y;
            }
        }
    for (auto x:E[u]) if (x.first==pos) work(x.second);
    printf("%lld\n",ans);exit(0);
}
int main(){
    n=gi();m=gi();
    for (int i=1;i<n;++i){
        int u=gi(),v=gi(),w=gi();
        link(u,v,w);link(v,u,w);
    }
    for (int i=1;i<=m;++i) a[i]=gi(),b[i]=gi();
    w[0]=sum=n;getroot(1,0);solve(RT=root,0);
    work(RT);return 0;
}

第十四分块(前体)

description

给你一个序列\(a_i\)以及常数\(k\),每次询问区间\(l,r\)内选两个数使其异或和在二进制下恰好有\(k\)\(1\)的方案数。

\(n,m\le10^5,0\le a_i<2^{14}\)

solution

考虑莫队。

以移动右端点为例。每次移动右端点的时候,我们需要求出这个新增右端点与原区间内多少个数满足异或和在二进制下恰好有\(k\)\(1\)。这个东西可以差分,因为一共只有\(O(m\sqrt m)\)次的移动,所以从前往后、从后往前分两次预处理一遍,把需要用到的\(O(m\sqrt m)\)个值记下来就行了。

在预处理中需要快速统计个数,因为插入次数是\(O(n)\)而询问次数是\(O(m\sqrt m)\),所以只要支持\(O(\binom{14}{k})\)插入,\(O(1)\)询问即可。

但是这样空间复杂度过高,不能通过本题,需要考虑更深层次地挖掘莫队在左右端点移动中的一些性质。

可以发现,每次左右端点移动时会移动连续的一段,而这样的连续段只会有\(O(m)\)个。那么我们就可以考虑预处理每一次连续移动的答案。

以左端点\(L\)不动,右端点从\(R\)移动到\(R'(R<R')\)为例。根据差分,这里对答案的增量是\(\sum_{i=R+1}^{R'}s_{i,i-1}-s_{i,L-1}\),其中\(s_{i,j}\)表示\(a_i\)与前\(j\)个数中有多少满足异或和在二进制下恰好有\(k\)\(1\)。发现前半部分都是\(s_{i,i-1}\)可以直接预处理,后半部分可以挂链挂到\(L-1\)上,然后\(O(R'-R)\)地计算。

右端点不动,左端点移动的情况恰好相反,同样处理即可。

code

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<vector>
using namespace std;
int gi(){
    int x=0,w=1;char ch=getchar();
    while ((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
    if (ch=='-') w=0,ch=getchar();
    while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
    return w?x:-x;
}
#define ll long long
const int N = 1e5+5;
int n,m,k,blk,a[N],zt[3433],cnt,totL,totR,sum[16384];
ll pre[N],suf[N],ans[N],Ans;
struct query{
    int l,r,id;
    bool operator < (const query &b) const
        {return l/blk==b.l/blk?r<b.r:l/blk<b.l/blk;}
}q[N];
struct node{int p,l,r;ll res;}QL[N],QR[N];
vector<int>VL[N],VR[N];
int main(){
    n=gi();m=gi();k=gi();blk=sqrt(n)+1;
    if (k>14) {while (m--) puts("0");return 0;}
    for (int i=1;i<=n;++i) a[i]=gi();
    for (int i=1;i<=m;++i) q[i]=(query){gi(),gi(),i};
    sort(q+1,q+m+1);
    for (int i=0;i<16384;++i){
        int j=0,x=i;while (x) ++j,x^=x&-x;
        if (j==k) zt[++cnt]=i;
    }
    for (int i=1,L=1,R=0;i<=m;++i){
        if (R<q[i].r) QL[++totL]=(node){L-1,R+1,q[i].r},R=q[i].r;
        if (L>q[i].l) QR[++totR]=(node){R+1,q[i].l,L-1},L=q[i].l;
        if (R>q[i].r) QL[++totL]=(node){L-1,q[i].r+1,R},R=q[i].r;
        if (L<q[i].l) QR[++totR]=(node){R+1,L,q[i].l-1},L=q[i].l;
    }
    for (int i=1;i<=totL;++i) VL[QL[i].p].push_back(i);
    for (int i=1;i<=totR;++i) VR[QR[i].p].push_back(i);
    for (int i=1;i<=n;++i){
        pre[i]=pre[i-1]+sum[a[i]];
        for (int j=1;j<=cnt;++j) ++sum[a[i]^zt[j]];
        for (int j:VL[i])
            for (int k=QL[j].l;k<=QL[j].r;++k)
                QL[j].res+=sum[a[k]];
    }
    memset(sum,0,sizeof(sum));
    for (int i=n;i;--i){
        suf[i]=suf[i+1]+sum[a[i]];
        for (int j=1;j<=cnt;++j) ++sum[a[i]^zt[j]];
        for (int j:VR[i])
            for (int k=QR[j].l;k<=QR[j].r;++k)
                QR[j].res+=sum[a[k]];
    }
    totL=totR=0;
    for (int i=1,L=1,R=0;i<=m;++i){
        if (R<q[i].r) Ans+=pre[q[i].r]-pre[R]-QL[++totL].res,R=q[i].r;
        if (L>q[i].l) Ans+=suf[q[i].l]-suf[L]-QR[++totR].res,L=q[i].l;
        if (R>q[i].r) Ans-=pre[R]-pre[q[i].r]-QL[++totL].res,R=q[i].r;
        if (L<q[i].l) Ans-=suf[L]-suf[q[i].l]-QR[++totR].res,L=q[i].l;
        ans[q[i].id]=Ans;
    }
    for (int i=1;i<=n;++i) printf("%lld\n",ans[i]);
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/zhoushuyu/p/9664856.html
今日推荐