NOIP2023模拟9联测30

这篇博客是第二天赛时写的。(恼)

T1

数学题。

肯定是想把 \(k\) 质因数分解,然后找一找规律,发现对于一个最小的 \(n\) 一定不包括除了 \(k\) 有的质因子以外的其他质因子,因为其他质因子对是不是 \(k\) 的倍数没有用。

\(n^2\) 相当于把 \(n\) 的所有质因子的指数乘了个 \(2\), 那么只要保证这个指数乘 \(2\) 后大于等于 \(k\) 的相同的质因子的指数,那么就是满足条件,要保证最小,直接把 \(k\) 质因子指数除以二向上取整就好了。

发现数很大,没办法全部质因数分解。发现对于大于 \(\sqrt{k}\) 的质因子的指数一定不大于一,那直接算到 \(\sqrt{k}\), 剩下的直接乘就可以了。

记得判无解情况,即 \(n=k\) 时。

Code
#include <iostream>
#include <cstdio>

#define int long long

const int N=1e7+10;

using namespace std;

inline int read() {
int f=1, x=0;
char ch=getchar();
while(ch>‘9’ || ch<‘0’) {
if(ch==‘-’) f=-1;
ch=getchar();
}
while(ch>=‘0’ && ch<=‘9’) {
x=(x<<3)+(x<<1)+(ch^48);
ch=getchar();
}
return f*x;
}

inline void write(int x) {
cout<<x; putchar(‘\n’);
}

int k, tot, ans;
int prime[N], is_prime[N], cnt[N];

void pre_work() {
for(int i=2;i<=N-5;i++) {
if(!is_prime[i]) prime[++tot]=i;
for(int j=1;j<=tot && prime[j]*i<=N-5;j++) {
is_prime[prime[j]*i]=1;
if(i%prime[j]==0) break;
}
}
}

inline int get_(int x,int k) {
if(x%k==0) return x/k;
else return (x/k)+1;
}

inline int fpow(int x,int k) {
int res=1;
while(k) {
if(k&1) res*=x;
x=x*x;
k>>=1;
}
return res;
}

signed main() {

pre_work();

k=read();

int now=k;
for(int i=1;i&lt;=tot &amp;&amp; prime[i]&lt;=now;i++) {
    if(now%prime[i]==0) {
        while(now%prime[i]==0) {
            cnt[i]++;
            now/=prime[i];
        }
    }
}

int ans=now;
for(int i=1;i&lt;=tot;i++) {
    if(cnt[i]) {
        ans=ans*fpow(prime[i], get_(cnt[i], 2));
    }
}
if(ans==k) write(-1);
else write(ans);

return 0;

}

T2

Part1

发现值域只有 \([1,9]\),那么直接分讨,对于 \(1, 5, 7\) 这三个数,和其他数的 \(gcd\) 都为 \(1\),又因为相同数没有区别,所以他们仨可以随便放。先分出来单独讨论。

Part2

剩下的 \(2,3,4,6,8,9\),发现他们的公共质因子只有 \(2,3\), 而 \(6\) 又都包含,说明 \(6\) 不能换,只能在这里,那么若干个 \(6\) 把序列分成了若干段,段与段之间互不影响。

考虑段内的贡献,发现公共质因子为 \(2\) 的几个数相对位置不会变化,为 \(3\) 的也不会变化,这两种数没有交集,互不影响,那么可以直接组合数算,设公共质因子为 \(2\) 的数有 \(n\) 个,为 \(3\) 的数有 \(m\) 个,那贡献就是 \(\dbinom{n+m}{n}\),最后全部乘起来就好了。

对于 \(1,5,7\) 的贡献,和上面差不多,设 \(1\) 的总数为 \(n\),当前数的个数为 \(m\),贡献即为 \(\dbinom{n+m}{n}\),\(5\) 和 \(7\) 也是一样的。

复杂度为 \(\Theta(n)\)。

Code
#include <iostream>
#include <cstdio>

#define int long long

const int mod=998244353;
const int N=1e5+10;

using namespace std;

inline int read() {
int f=1, x=0;
char ch=getchar();
while(ch>‘9’ || ch<‘0’) {
if(ch==‘-’) f=-1;
ch=getchar();
}
while(ch>=‘0’ && ch<=‘9’) {
x=(x<<3)+(x<<1)+(ch^48);
ch=getchar();
}
return f*x;
}

inline void write(int x) {
cout<<x; putchar(‘\n’);
}

int n, tot, ans;
int a[N], cnt[N];
int fac[N], inv[N], invj[N];

void prework() {
fac[0]=fac[1]=inv[0]=inv[1]=invj[0]=invj[1]=1;
for(int i=2;i<=n+5;i++) {
fac[i]=fac[i-1]*i%mod;
inv[i]=(mod-mod/i)*inv[mod%i]%mod;
invj[i]=invj[i-1]*inv[i]%mod;
}
ans=1;
}

inline int C(int n,int m) {
if(m>n) return 0;
return fac[n]invj[n-m]%modinvj[m]%mod;
}

inline void solve(int l,int r) {
if(l>=r) return;
int sum1=0, sum2=0;
for(int i=l;i<=r;i++) {
if(a[i]%2==0) sum1++;
else sum2++;
}
int sum=C(sum1+sum2, sum1);
ans=(ans*sum)%mod;
}

signed main() {

n=read();

prework();

for(int i=1;i&lt;=n;i++) {
    int x=read();
    if(x==1 || x==5 || x==7) {
        cnt[x]++;
    }
    else a[++tot]=x;
}

int pre=1;
for(int i=1;i&lt;=tot;i++) {
    if(a[i]==6) {
        solve(pre, i-1);
        pre=i+1;
    }
}
solve(pre, tot);

tot+=cnt[1], ans=(ans*C(tot, cnt[1]))%mod;
tot+=cnt[5], ans=(ans*C(tot, cnt[5]))%mod;
tot+=cnt[7], ans=(ans*C(tot, cnt[7]))%mod;

write(ans);

return 0;

}

T3

Part1

设最终的力量值为 \(x\),首先发现将 \(x\) 设置为原有的一条龙的力量值一定更优,证明考虑移动 \(x\),在龙与龙之间时的图像是一条直线,那么最值一定出在一个拐点上,也就是原有的一条龙的力量值。

考虑刻画出函数图像,对于单个一条龙,函数图像一定是一个凸函数,又因为凸函数也是凸函数,那么多条龙的函数的加和也一定是凸函数,那么可以直接三分找到最值,只不过复杂度为 \(\Theta(n\log^2n)\),好像可以过。

Part2

考虑那个凸函数各部分的斜率,发现越接近答案,斜率越小,可以通过这个直接求出答案拐点的排名。

设答案拐点前有 \(P\) 个点,那么从第 \(P\) 个点到第 \(P+1\) 个点的斜率一定是最小的,单位变化量也是最小的。想象一个点在图像上移动,每移动 \(1\) 的力量值,\(\Delta f(x)=a\times P-b\times(i-P) \leqslant 0\),解得 \(P \leqslant \dfrac{b\times i}{a+b}\), 这个 \(P\) 一定是最大的,所以取等,答案拐点的排名即为 \(P+1\)。

Part3

代码实现上,先离散化,因为要动态查询排名为 \(K\) 的值,快速求出以 \(x\) 为最终的力量值的总代价,所以直接权值线段树就行了,然后一个一个地加,一个一个地求,复杂度 \(\Theta(n\log n)\)。

Code
#include <iostream>
#include <cstdio>
#include <cstring>
#include <map>
#include <algorithm>

#define int long long

const int N=1e6+10;

using namespace std;

inline int read() {
int f=1, x=0;
char ch=getchar();
while(ch>‘9’ || ch<‘0’) {
if(ch==‘-’) f=-1;
ch=getchar();
}
while(ch>=‘0’ && ch<=‘9’) {
x=(x<<3)+(x<<1)+(ch^48);
ch=getchar();
}
return f*x;
}

inline void write(int x) {
cout<<x; putchar(‘\n’);
}

int n, a, b;
int d[N], mp[N], ans[N];

map <int,int> Kth;

struct Tree {
int sum, size;
}t[N<<2];

struct Seg_Tree {

inline Tree merge(Tree ls, Tree rs) {
    Tree res=(Tree){0, 0};
    res.size=ls.size+rs.size;
    res.sum=ls.sum+rs.sum;
    return res;
}

inline void push_up(int k) {
    t[k]=merge(t[k&lt;&lt;1], t[k&lt;&lt;1|1]);
}

void change(int k,int l,int r,int pos,int val) {
    if(l==r) {
        t[k].sum+=val*mp[l];
        t[k].size+=val;
        return;
    }
    int mid=(l+r)&gt;&gt;1;
    if(pos&lt;=mid) change(k&lt;&lt;1, l, mid, pos, val);
    else change(k&lt;&lt;1|1, mid+1, r, pos, val);
    push_up(k);
}

int find(int k,int l,int r,int K) {
    if(l==r) return l;
    int mid=(l+r)&gt;&gt;1;
    if(t[k&lt;&lt;1].size&gt;=K) return find(k&lt;&lt;1, l, mid, K);
    else return find(k&lt;&lt;1|1, mid+1, r, K-t[k&lt;&lt;1].size);
}

Tree query(int k,int l,int r,int L,int R) {
    if(L&lt;=l &amp;&amp; r&lt;=R) return t[k];
    int mid=(l+r)&gt;&gt;1;
    Tree ls=(Tree){0, 0}, rs=(Tree){0, 0};
    if(L&lt;=mid) ls=query(k&lt;&lt;1, l, mid, L, R);
    if(R&gt;mid) rs=query(k&lt;&lt;1|1, mid+1, r, L, R);
    return merge(ls, rs);
}

}T;

signed main() {

n=read(), a=read(), b=read();
for(int i=1;i&lt;=n;i++) {
    d[i]=read();
    mp[i]=d[i];
}

sort(mp+1, mp+n+1);
int Len=unique(mp+1, mp+n+1)-mp-1;
for(int i=1;i&lt;=Len;i++) Kth[mp[i]]=i;

for(int i=1;i&lt;=n;i++) {
    T.change(1, 1, n, Kth[d[i]], 1);
    int p=(b*i)/(a+b);
    int pos=T.find(1, 1, n ,p+1);
    Tree ll=(Tree){0, 0}, rr=(Tree){0, 0};
    if(pos-1&gt;=1) ll=T.query(1, 1, n, 1, pos-1);
    if(pos+1&lt;=n) rr=T.query(1, 1, n ,pos+1, n);
    ans[i]=(mp[pos]*ll.size-ll.sum)*a+(rr.sum-mp[pos]*rr.size)*b;
}

for(int i=1;i&lt;=n;i++) write(ans[i]);

return 0;

}

T4

\(T4\) 的难度有点水吧?

Part1

根据初中数学知识:

\(a^x·a^y=a^{x+y}\)
\((a_1+a_2+\dots+a_n)·(b_1+b_2+\dots+b_m)=a_1b_1+a_1b_2+\dots+a_1b_m+a_2b_1+a_2b_2+\dots+a_nb_m\)

那么可以直接把贡献拆开,\(u\) 的一部分,\(v\) 的一部分,\(u\) 和 \(v\) 之间的路径。最后直接乘起来就行了。

Part2

设 \(f_i=\sum_{u\in sutree_i}2^{dis_{u,i}}\),$ g_i=\sum_{u=1}^{n}dis_{u,i}\(。\)f_i$ 就是它的子树内的点对他的贡献,\(g_i\) 是所有点对它的贡献。

分为两种情况:

第一种情况,\(u\) 和 \(v\) 都不是 \(lca\),那么经过 \(u\) 和 \(v\) 的路径的两个端点一定一个在 \(u\) 的子树,一个在 \(v\) 的子树,那么答案就是 \(f_u\times f_v \times 2^{dis_u,v}\),这个好算。

第二种情况,\(u\) 和 \(v\) 其中有一个是 \(lca\),默认非 \(lca\) 的那个点为 \(u\),那么经过 \(u\) 和 \(v\) 的路径的两个端点,一个在 \(u\) 子树内,另一个在 把 \(u\) 所在的 \(lca\) 的子树的所有节点刨除后,剩下的其他节点上,画一画图就明白了,我们把 \(u\) 所在的 \(lca\) 的子树的根节点称为 \(clca\),那么答案就是 \(f_u \times (g_{lca}-2\times f_{clca} )\times 2^{dis_{u,lca}}\)。

Part3

实现上,可以用 换根\(dp\) 在 \(\Theta(n)\) 复杂度内求出 \(f_i\) 和 \(g_i\)。(也可以用点分治,只不过复杂度劣一些,\(\Theta(n\log n)\) 加上大常数,可能过不去,而且还相对难写)

求 \(lca\) 和 \(clca\) 可以用树剖(倍增常数太大),也可以用 \(Tarjan\) 离线求。

如何用树剖求 \(clca\),先找到 \(lca\),然后从 \(u\) 往 \(lca\) 跳,记录当前链的链顶,跳到链顶相同后开始分讨。

  • \(u=lca\),也就是上一条链的链顶的父亲是 \(lca\),\(clca\) 和 \(lca\) 不在同一条重链上,那么上一条链的链顶就是 \(clca\)。

  • 剩下的情况就是 \(clca\) 和 \(lca\) 在同一条重链上,因为重链上的 \(dfs\)序 是连续的,那么 \(clca\) 的 \(dfs\)序 就是 \(lca\) 的下一位。预处理时记录一下 \(dfs\)序 就可以了。

复杂度为 \(\Theta(q\log n)\),轻微卡常。

Code
#include <iostream>
#include <cstdio>

#define rint register int
#define ll long long

const int N=1e6+10;
const ll mod=998244353;

using namespace std;

inline int read() {
int f=1, x=0;
char ch=getchar();
while(ch>‘9’ || ch<‘0’) {
if(ch==‘-’) f=-1;
ch=getchar();
}
while(ch>=‘0’ && ch<=‘9’) {
x=(x<<3)+(x<<1)+(ch^48);
ch=getchar();
}
return f*x;
}

inline void write(ll x) {
cout<<x; putchar(‘\n’);
}

int n, cnt_edge;
struct edge {
int next ,to;
}e[N<<1];
int head[N];

inline void add_edge(int u,int v) {
e[++cnt_edge].to=v;
e[cnt_edge].next=head[u];
head[u]=cnt_edge;
}

ll vs[N], vr[N];
void dfs1(int now,int fa) {
vs[now]=1;
for(rint i=head[now];i;i=e[i].next) {
int v=e[i].to;
if(v==fa) continue;
dfs1(v, now);
vs[now]=(vs[now]+vs[v]*2ll)%mod;
}
}

void dfs2(int now,int fa) {
ll sum=(vr[fa]+mod-vs[now]2ll)%mod;
vr[now]=(vs[now]+sum
2ll)%mod;
for(rint i=head[now];i;i=e[i].next) {
int v=e[i].to;
if(v==fa) continue;
dfs2(v, now);
}
}

int tim, dfn[N], mp[N];
int dep[N], siz[N], fa[N], top[N], son[N];
struct Heavy {
void dfs1(int now,int father) {
dep[now]=dep[father]+1, siz[now]=1;
fa[now]=father, son[now]=0;
int maxson=-1;
for(rint i=head[now];i;i=e[i].next) {
int v=e[i].to;
if(vfather) continue;
dfs1(v, now);
siz[now]+=siz[v];
if(siz[v]>maxson) {
maxson=siz[v], son[now]=v;
}
}
}
void dfs2(int now,int topf) {
dfn[now]=++tim, mp[tim]=now;
top[now]=topf;
if(son[now]) dfs2(son[now], topf);
for(rint i=head[now];i;i=e[i].next) {
int v=e[i].to;
if(v
son[now] || v==fa[now]) continue;
dfs2(v, v);
}
}

inline int LCA(int x,int y) {
    while(top[x]!=top[y]) {
        if(dep[top[x]] &lt; dep[top[y]]) x^=y, y^=x, x^=y;
        x=fa[top[x]];
    }
    if(dep[x] &gt; dep[y]) x^=y, y^=x, x^=y;
    return x;
}

inline int CLCA(int x,int y) {
    int topf=0;
    while(top[x]!=top[y]) {
        topf=top[x];
        x=fa[top[x]];
    }
    if(x==y) return topf;
    else if(fa[x]==y) return x;
    else return mp[dfn[y]+1];
}

}H;

ll power[N];
void prework() {
dfs1(1, 0);
vr[1]=vs[1];
for(rint i=head[1];i;i=e[i].next) {
int v=e[i].to;
dfs2(v, 1);
}
power[0]=1;
for(rint i=1;i<=n+5;++i) {
power[i]=power[i-1]*2ll%mod;
}
H.dfs1(1, 0);
H.dfs2(1, 1);
}

signed main() {

n=read();
for(rint i=1;i&lt;n;++i) {
    int u=read(), v=read();
    add_edge(u, v);
    add_edge(v, u);
}

prework();

int q=read();
for(rint tur=1;tur&lt;=q;++tur) {
    int u=read(), v=read();
    int lca=H.LCA(u, v);
    int dis=dep[u]+dep[v]-2ll*dep[lca];
    ll ans=power[dis];
    if(u==lca || v==lca) {
        if(u==lca) u=v;
        int clca=H.CLCA(u, lca);
        ans=(ans*vs[u]%mod*((vr[lca]-vs[clca]*2ll+mod)%mod))%mod;
    }
    else ans=ans*vs[u]%mod*vs[v]%mod;
    write((ans+mod)%mod);
}

return 0;

}

NOIP2023模拟9联测30
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值