2018-2019 Summer Petrozavodsk Camp, Oleksandr Kulkov Contest 2

链接

C. Money Sharing

反悔贪心。考虑每次加入一个请求,如果不可行一定会删掉之前出现的最大的一次请求。

可以发现删掉这次请求不会导致更多请求被加入。

复杂度 O(nlogn)

#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#define N 200010
#define ll long long
using namespace std;
struct node{
    int u,v;
    bool operator <(const node a)const{return v<a.v;}
};
priority_queue<node>q;
int ans[N];
int main()
{
    int n,m;
    scanf("%d%d",&n,&m);n+=m;
    ll sm=0;
    for(int i=1;i<=n;i++)
    {
        int u;scanf("%d",&u);
        if(u>0){ans[i]=-1;sm+=u;continue;}
        u=-u;
        node p;
        if(u<=sm) ans[i]=1,sm-=u;
        else if(!q.empty() && u<=sm+(p=q.top()).v && u<p.v)
        {
            sm=sm+p.v-u;
            ans[i]=1;ans[p.u]=0;q.pop();
        }
        else continue;
        q.push((node){i,u});
    }
    for(int i=1;i<=n;i++)
        if(ans[i]==-1) puts("resupplied");
        else if(ans[i]==0) puts("declined");
        else puts("approved");
    return 0;
}

E. Decimal Expansion

找规律题,用 python 手算 计算器打表发现是 89...0...10...9... 的循环,并且循环的长度会逐渐增长。具体证明可以把那个东西看成 (10i1),用五边形数证明。

直接二分找到对应的循环点。复杂度 O(logn)

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#define N 100010
#define ll long long
using namespace std;
ll calc(ll x){return x>1e9?1e18+5:(3*x*x-x)/2;}
ll calc2(ll x){return x>1e9?1e18+5:(3*x*x+x)/2;}
int main()
{
    int t;
    scanf("%d",&t);
    while(t --> 0)
    {
        ll n;
        scanf("%lld",&n);
        long long x=0;
        for(int k=31;~k;k--) if(calc(x+(1ll<<k))<n) x+=1ll<<k;
        x++;
        if(calc(x)==n) printf("%d ",x&1?8:1);
        else
        {
            ll y=0;
            for(int k=31;~k;k--) if(calc2(y+(1ll<<k))<n) y+=1ll<<k;
            printf("%d ",y&1?0:9);
        }
    }
    return 0;
}

B. Yet Another Convolution

考虑处理 k=1 的情况。因为后面的情况直接暴力枚举 k 只会多一个 lnn

这样题目变成求下标互质的两序列极差。显然可以想到莫比乌斯反演。但是最大值不太好反演,考虑二分答案,这样就变成求差大于某个数字的个数,大力枚举 gcd 然后双指针算对数,复杂度 O(nlogVlnn)

总复杂度 O(nlogVln2n)。常数很小,可以通过。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#include<algorithm>
#define N 100010
using namespace std;
typedef vector<int> vec;
vec a[N],b[N];
int p[N],mu[N],cnt;bool pr[N];
void init(int n=N-10)
{
    mu[1]=1;
    for(int i=2;i<=n;i++)
    {
        if(!pr[i]){mu[i]=-1;p[++cnt]=i;}
        for(int j=1;j<=cnt && i*p[j]<=n;j++)
        {
            pr[i*p[j]]=true;
            if(i%p[j]==0) break;
            mu[i*p[j]]=-mu[i];
        }
    }
}
int main()
{
    int n;
    scanf("%d",&n);
    init();
    a[1].resize(n+1);b[1].resize(n+1);
    for(int i=1;i<=n;i++) scanf("%d",&a[1][i]);
    for(int i=1;i<=n;i++) scanf("%d",&b[1][i]);
    for(int i=2;i<=n;i++)
    {
        a[i].resize(n/i+1);b[i].resize(n/i+1);
        for(int j=1;i*j<=n;j++) a[i][j]=a[1][i*j],b[i][j]=b[1][i*j];
    }
    for(int i=1;i<=n;i++) sort(a[i].begin()+1,a[i].end()),sort(b[i].begin()+1,b[i].end());
    for(int t=1;t<=n;t++)
    {
        auto check=[&](int k,vec a[],vec b[]){
            long long res=0;
            for(int d=1;t*d<=n;d++)
            {
                int c=0;
                for(int i=1,j=1;i*d*t<=n;i++)
                {
                    while(j*d*t<=n && b[t*d][j]<=a[t*d][i]-k) j++;
                    c+=j-1;
                }
                res+=mu[d]*c;
            }
            return !!res;
        };
        int l=0,r=1e9,res=0;
        while(l<=r)
        {
            int mid=(l+r)>>1;
            if(check(mid,a,b) || check(mid,b,a)) l=mid+1,res=mid;
            else r=mid-1;
        }
        printf("%d ",res);
    }
    return 0;
}

J. Tree Automorphisms

考虑什么情况下会有这样置换的出现:显然如果一个置换本身有不动点,那么不动点连出的几棵树必须同构,本质上就是对同构的树两两交换。

如果没有不动点,那么必然是点之间两两交换,这样一定有一个中心在边上,且只能是重点之间的连边。

判同构可以用树哈希。这里怎么大力做都可以,复杂度 O(n?)

#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#include<algorithm>
#define N 110
#define pb push_back
#define S(x) ((int)x.size())
#define MP make_pair
#define fi first
#define se second
using namespace std;
int n;
vector<int>g[N];
namespace Root{
    int siz[N],mx,rt;
    void dfs(int u,int p)
    {
        siz[u]=1;
        int mr=0;
        for(int v:g[u]) if(v!=p) dfs(v,u),siz[u]+=siz[v],mr=max(mr,siz[v]);
        mr=max(mr,n-siz[u]);
        if(!rt || mx>mr) rt=u,mx=mr;
    }
    int find(){dfs(1,0);return rt;}
}
typedef vector<int> vec;
typedef vector<vec> vvec;
typedef pair<string,vec> pvec;
vvec ans;
void add(const vec &a,const vec &b)
{
    vec c(n);
    for(int i=0;i<n;i++) c[i]=i+1;
    for(int i=0;i<S(a);i++) swap(c[a[i]-1],c[b[i]-1]);
    ans.pb(c);
}
void operator +=(vec &a,const vec &b){for(int d:b) a.pb(d);}
pvec dfs(int u,int p)
{
    vector<pvec> son;
    for(int v:g[u]) if(v!=p) son.push_back(dfs(v,u));
    sort(son.begin(),son.end());
    for(int i=0;i<S(son);i++)
        for(int j=i+1;j<S(son) && son[i].fi==son[j].fi;i++,j++) add(son[i].se,son[j].se);
    pvec a;
    a.se.pb(u);
    for(auto v:son) a.fi+="0"+v.fi+"1",a.se+=v.se;
    return a;
}
int main()
{
    scanf("%d",&n);
    for(int i=1,u,v;i<n;i++) scanf("%d%d",&u,&v),g[u].pb(v),g[v].pb(u);
    int u=Root::find();Root::dfs(u,0);
    bool only=true;
    for(int v:g[u])
    if(Root::siz[v]*2==n)
    {
        auto f=dfs(u,v),g=dfs(v,u);
        if(f.fi==g.fi) add(f.se,g.se);
        only=false;break;
    }
    if(only) dfs(u,0);
    if(ans.empty()) add(vec(),vec());
    printf("%d\n",S(ans));
    for(auto p:ans)
    {
        for(auto x:p) printf("%d ",x);
        puts("");
    }
    return 0;
}

H. Defying Gravity

诈骗题。

首先题目给了一个奇怪的式子,其实就是真 · 万有引力公式,即力的方向指向天体,力的大小与质量成正比,与半径的二次方成反比。

但这题没有这么麻烦。由于题目要求方向始终不变,不难发现方向左右的天体应当是对称的,不然一定会有一个位置受力方向改变。

也就是说把质量和距离合起来当做关键字,角度当做下标,这就是一个长度为 129600 的字符串环,要求它的对称中心。显然怎么做都可以。

复杂度 O(n+129600)代码咕咕咕了

A. Square Root Partitioning

如果将 x 分解为 ab 的形式,那么可以根据 b 来分类分别处理,通过 meet in the middle 可以在 O(2n2poly(n)) 内完成。

但是这里 x 非常大,很难分解质因数。所以考虑使用一个大质数 P,可以发现:如果一个数 xP 的一个二次剩余,那么直接取 x=xP+14modP

但有可能 x 并不是 P 的一个二次剩余,这样算出的结果是 (a+bi) 的形式。i 是一个域外的数字,但是根据勒让德符号的性质,如果 a,b 都不是二次剩余,那么 ab 一定是二次剩余,所以 i 在上述那种运算下映射得到的数是唯一的,换句话说我们可以把这个数字当成 x

这样唯一的问题在于这个 i 映射的位置可能原来就有值,这样会导致碰撞。但是只要质数 P 取很大,上述碰撞的概率就会极小。

复杂度 O(n2n2)

#include<iostream>
#include<cstdio>
#include<cstring>
#include<map>
#define N 110
#define ll long long
using namespace std;
const ll mod=411191981019260843;
ll mul(ll a,ll b){return ((a*b-(ll)((long double)a/mod*b+0.5)*mod)%mod+mod)%mod;}
ll ksm(ll a,ll b=(mod+1)/4)
{
    ll r=1;
    for(;b;b>>=1,a=mul(a,a)) if(b&1) r=mul(r,a);
    return r;
}
ll read()
{
    int ch=0;ll x=0;
    while(!isdigit(ch)) ch=getchar();
    while(isdigit(ch)) x=(x*10+ch-'0')%mod,ch=getchar();
    return x;
}
ll a[N];
map<ll,int>st;
int main()
{
    int n;
    scanf("%d",&n);
    for(int i=0;i<n;i++) a[i]=ksm(read());
    // for(int i=0;i<n;i++) printf("%lld\n",a[i]);
    int m=n/2;n-=m;
    for(int s=0;s<1<<m;s++)
    {
        ll x=0;
        for(int i=0;i<m;i++) if(s>>i&1) x=(x+a[i])%mod;else x=(x-a[i])%mod;
        st[(mod-x)%mod]++;
    }
    ll ans=0;
    for(int s=0;s<1<<n;s++)
    {
        ll x=0;
        for(int i=0;i<n;i++) if(s>>i&1) x=(x+a[m+i])%mod;else x=(x-a[m+i])%mod;
        x=(x+mod)%mod;
        if(st.count(x)) ans+=st[x];
    }
    printf("%lld\n",ans/2);
    return 0;
}

I. From Modular to Rational

首先搞出用两个质数拼出一个大模数 M 意义下的结果 X。由于 p,q 不超过 109,所以 q 对应的 p 合法当且仅当 qXMqX1091M。这个可以用类欧几里得算出前缀和,然后二分找到第一个合法点。

注意由于 C++ 的特性,负数的整除是对 0 取整而不是向下取整,所以要避免用到负数,即将上面右边的式子加上 M

复杂度 O(tlog2n),被卡常了。

还有一种 tlogn 的高妙做法,不太会。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<ctime>
#define N 100010
#define ll long long
using namespace std;
inline ll adj(const ll &a,const ll &mod){return a>=mod?a-mod:(a<0?a+mod:a);}
inline ll mul(const ll &a,const ll &b,const ll &mod){return adj(a*b-(ll)(1.0L*a/mod*b+0.5)*mod,mod);}
inline ll ksm(ll a,ll b,ll mod)
{
    ll r=1;
    for(;b;b>>=1,a=mul(a,a,mod)) if(b&1) r=mul(r,a,mod);
    return r;
}
const int m1=1000000007,m2=1000000009,iv2=500000004;
const ll M=1ll*m1*m2,dta=1000000001;
ll c1,c2,X;
ll F(ll a,ll b,ll c,ll n)
{
    if(a>=c || b>=c) return n*(n+1)/2*(a/c)+(n+1)*(b/c)+F(a%c,b%c,c,n);
    if(n==0 || a==0) return 0;
    ll m=((__int128)a*n+b)/c;
    return n*m-F(c,c-b-1,a,m-1);
}
int main()
{
    // srand(time(0));
    int t;
    scanf("%d",&t);
    while(t --> 0)
    {
        // ll p,q;
        // scanf("%lld%lld",&p,&q);
        printf("? %d\n",m1);fflush(stdout);
        scanf("%lld",&c1);/*c1=p*ksm(q,m1-2,m1)%m1;*/
        printf("? %d\n",m2);fflush(stdout);
        scanf("%lld",&c2);/*c2=p*ksm(q,m2-2,m2)%m2;*/
        X=mul((m2*c1+m1*c2)%M,iv2,M);
        // printf("%lld\n",X);
        ll l=1,r=1e9;
        while(l<r)
        {
            ll mid=(l+r)>>1;
            if(F(X,0,M,mid)!=F(X,M-dta,M,mid)-mid) r=mid;
            else l=mid+1;
        }
        // if(mul(r,X,M)>1e9){printf("%d %d\n",p,q);return 0;}
        printf("! %lld %lld\n",mul(r,X,M),r);
    }
    return 0;
}
posted @   Flying2018  阅读(281)  评论(0编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
历史上的今天:
2020-07-14 Grammy's Restaurant
点击右上角即可分享
微信分享提示