2018-2019 Summer Petrozavodsk Camp, Oleksandr Kulkov Contest 2
链接
C. Money Sharing
反悔贪心。考虑每次加入一个请求,如果不可行一定会删掉之前出现的最大的一次请求。
可以发现删掉这次请求不会导致更多请求被加入。
复杂度 。
#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... 的循环,并且循环的长度会逐渐增长。具体证明可以把那个东西看成 ,用五边形数证明。
直接二分找到对应的循环点。复杂度 。
#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
考虑处理 的情况。因为后面的情况直接暴力枚举 只会多一个 。
这样题目变成求下标互质的两序列极差。显然可以想到莫比乌斯反演。但是最大值不太好反演,考虑二分答案,这样就变成求差大于某个数字的个数,大力枚举 然后双指针算对数,复杂度 。
总复杂度 。常数很小,可以通过。
#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
考虑什么情况下会有这样置换的出现:显然如果一个置换本身有不动点,那么不动点连出的几棵树必须同构,本质上就是对同构的树两两交换。
如果没有不动点,那么必然是点之间两两交换,这样一定有一个中心在边上,且只能是重点之间的连边。
判同构可以用树哈希。这里怎么大力做都可以,复杂度 。
#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 的字符串环,要求它的对称中心。显然怎么做都可以。
复杂度 。代码咕咕咕了。
A. Square Root Partitioning
如果将 分解为 的形式,那么可以根据 来分类分别处理,通过 meet in the middle 可以在 内完成。
但是这里 非常大,很难分解质因数。所以考虑使用一个大质数 ,可以发现:如果一个数 是 的一个二次剩余,那么直接取 。
但有可能 并不是 的一个二次剩余,这样算出的结果是 的形式。 是一个域外的数字,但是根据勒让德符号的性质,如果 都不是二次剩余,那么 一定是二次剩余,所以 在上述那种运算下映射得到的数是唯一的,换句话说我们可以把这个数字当成 。
这样唯一的问题在于这个 映射的位置可能原来就有值,这样会导致碰撞。但是只要质数 取很大,上述碰撞的概率就会极小。
复杂度 。
#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
首先搞出用两个质数拼出一个大模数 意义下的结果 。由于 不超过 ,所以 对应的 合法当且仅当 。这个可以用类欧几里得算出前缀和,然后二分找到第一个合法点。
注意由于 C++ 的特性,负数的整除是对 0 取整而不是向下取整,所以要避免用到负数,即将上面右边的式子加上 。
复杂度 ,被卡常了。
还有一种 的高妙做法,不太会。
#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;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 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