11.10总结
11.10 GZEZ NOIP2022模拟测试赛(五十八)
Problem A :
题面描述 :
有\(T\)组询问,每组询问给定\(n,m,a\) 。
求 \(\sum \limits_{i = 1}^{n} \sum \limits_{j=1}^{m}lcm\left(i,j\right)\left[gcd\left(i,j\right)\le a\right]\)
Solution:
别人:好,一眼切了
我:我眼呢,💢💢这是\(Crash\)加强版,来推狮子:
\[\text{先枚举gcd } \sum \limits_{k=1}^{a} \sum \limits_{i=1}^{n}\sum \limits_{j=1}^{m}\frac{i*j}{k} \left[gcd\left(i,j\right)==1\right]\ 莫反一下\sum \limits_{k=1}^{a} \sum \limits_{i=1}^{n}\sum \limits_{j=1}^{m}\frac{i*j}{k}\sum\limits_{d\mid gcd(i,j)}\mu(d)\\ 再划一下把k乘过来\sum \limits_{k=1}^{a} \sum \limits_{i=1}^{[\frac{n}{k}]} \sum \limits_{j=1}^{[\frac{m}{k}]} {i*j*k} \sum\limits_{d\mid gcd(i,j)}\mu(d)\\ 把d\mid gcd拆开\sum \limits_{k=1}^{min(n,m)} \sum \limits_{i=1}^{[\frac{n}{k}]}\sum \limits_{j=1}^{[\frac{m}{k}]}{i*j*k}\sum\limits_{d\mid i,d\mid j}\mu(d)\\ 提到前面去\sum \limits_{k=1}^{a}k\sum\limits_{d=1}^{min(n,m)}\mu(d)\sum \limits_{i=1}^{[\frac{n}{k}]} i[d\mid i]\sum \limits_{j=1}^{[\frac{m}{k}]}{j[d\mid j]}\\ 把d除过去\sum \limits_{k=1}^{a}k\sum\limits_{d=1}^{min(n,m)}\mu(d)*d^2\sum \limits_{i=1}^{[\frac{n}{kd}]} {i}\sum \limits_{j=1}^{[\frac{m}{kd}]}{j}\\ \]现在解决了\(T=1\)的情况,考虑\(T=10000\)
\[T = kd\\ \sum \limits_{T=1}^{n} \sum \limits_{k=1}^{a} \mu(\frac{T}{k})*\frac{T^2}{k}\sum\limits_{i=1}^{[\frac{n}{kd}]}i\sum\limits_{j=1}^{[\frac{m}{kd}]}j\\ 后面两个求$i,j$可以O(1)算,求k后面数论分块根号的 \]考虑整数分块。 对于每⼀段\([\frac{n}{p}],[\frac{m}{p}]\) , 相同的分⼀块。 ⼀共只有\(\sqrt{n}\)块。 将询问按\(a\)排序。考虑在树状数组上放\(a\)的下标然后当你\(a\)到\(a+i\)的贡献去枚举d的倍数然后在树状数组上单点加
Code:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int SIZE = 1e5 + 10;
const int mod = 1e9 + 7;
int T, cnt;
int prime[SIZE], mo[SIZE], use[SIZE];
long long ans[SIZE];
struct node
{
int n, m, a ;
int id;
} q[SIZE];
inline bool cmp(node x ,node y){
return x.a < y.a ;
}
inline
namespace BIT
{
long long tree[SIZE];
#define lowbit(x) (x & -x)
int Limm = 1e5 ;
inline void add(int x, int k){
while(x <= Limm){
tree[x] += k ;
x += lowbit(x) ;
}
}
inline ll query(int x){
ll ans = 0;
while(x){
ans += tree[x] ;
x -= lowbit(x) ;
}
return (ans + mod) % mod;
}
inline ll query(int l, int r){
return (query(r) - query(l - 1) + mod) % mod;
}
inline void Add(int d)
{
for (ll T = d; T <= 1e5; T += d){
add(T, (ll)T * (T / d) % mod * mo[T / d]);
}
}
};
using namespace BIT ;
inline void init(int n){
mo[1] = 1 ;
for(int i = 2 ; i <= n ; i += 1){
if(!use[i]) prime[++cnt] = i , mo[i] = -1 ;
for(int j = 1 ; j <= cnt && i * prime[j] <= n ; j += 1){
use[i * prime[j]] = 1 ;
if(i % prime[j] == 0) break ;
mo[i * prime[j]] = -mo[i] ;
}
}
}
inline ll work(int x){
return x * (x + 1ll) / 2 % mod ;
}
inline ll calc(int n , int m){
int lim = min(n , m) ;
ll ans = 0 ;
for(int l = 1 , r ; l <= lim ; l = r + 1){
r = min(n / (n / l) , m / (m / l)) ;
ans += work(n / l) * work(m / l) % mod * query(l , r) % mod ;
}
return (ans % mod + mod) % mod ;
}
int main()
{
cin >> T ;
init(1e5);
for (int i = 1; i <= T; i += 1)
{
cin >> q[i].n >> q[i].m >> q[i].a ;
q[i].id = i;
}
sort(q + 1, q + T + 1, cmp) ;
for (int x = 1, i = 0; x <= 1e5 ; x += 1)
{
Add(x);
while (i < T and q[i + 1].a == x){
i += 1 ;
ans[q[i].id] = calc(q[i].n, q[i].m) ;
}
}
for (int i = 1; i <= T; i += 1){
cout << ans[i] << '\n' ;
}
}
Problem B :1
题面描述 :
给你一个连通的平面图(平面图是可以画在平面上并且使得不同的边可以互不交叠的图)。接下来有 \(q\) 次操作:
- 操作"-", 删去边 $x, y $, 询问删完后有几个连通块。
- 操作"?", 询问 $ x, y $ 在不在一个连通块中, 如果是, 输出 \(1\) , 否则输出 \(0\) 。
Solution:
因为没有 操作,所以只需要求出有哪些被删掉的边是关建边。 因为平⾯图中的⼀个割对应对偶图中的⼀个环,先求出对偶图,在⽤并查集维护对偶图上的连通性。
现在已经知道有哪些边是关键边了,需要动态维护所在连通块。 隔断⼀条关键边的时候考虑启发式分裂,给分裂出来的较小的连通块重新标号。
Code:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define cout io
#define cin io
#define endl '\n'
#ifdef ONLINE_JUDGE
#define getchar() ((p1==p2)and(p2=(p1=ibuf)+fread(ibuf,1,100000,stdin),p1==p2)?(EOF):(*p1++))
#define putchar(x) ((p3==obuf+100000)&&(fwrite(obuf,p3-obuf,1,stdout),p3=obuf),*p3++=x)
#endif
char ibuf[100000],obuf[100000];char*p1=ibuf,*p2=ibuf,*p3=obuf;const ll pow10[]={(ll)1e0,(ll)1e1,(ll)1e2,(ll)1e3,(ll)1e4,(ll)1e5,(ll)1e6,(ll)1e7,(ll)1e8,(ll)1e9,(ll)1e10,(ll)1e11,(ll)1e12,(ll)1e13,(ll)1e14,(ll)1e15,(ll)1e16,(ll)1e17,(ll)1e18,};struct Octane_t{~Octane_t(){fwrite(obuf,p3-obuf,1,stdout);}bool flag=false;operator bool(){return flag;}}io;template<typename T>inline T read(){T s=0;int w=1;char ch;while(ch=getchar(),!isdigit(ch)&&(ch!=EOF))if(ch=='-')w=-1;if(ch==EOF)return 0;while(isdigit(ch))s=s*10+ch-48,ch=getchar();return s*=w;}template<typename T>inline bool read(T&s){s=0;int w=1;char ch;while(ch=getchar(),!isdigit(ch)&&(ch!=EOF))if(ch=='-')w=-1;if(ch==EOF)return false;while(isdigit(ch))s=s*10+ch-48,ch=getchar();return s*=w,true;}inline bool read(char&s){while(s=getchar(),isspace(s));return s!=EOF;}inline bool read(char*s){char ch;while(ch=getchar(),isspace(ch));if(ch==EOF)return false;while((ch>32))*s++=ch,ch=getchar();*s='\000';return true;}template<typename T>void print(T x){static int t[20];int top=0;if(x<0)putchar('-'),x=-x;do{t[++top]=x%10;x/=10;}while(x);while(top)putchar(t[top--]+48);}struct empty_type{};int pcs=8;empty_type setpcs(int cnt){return pcs=cnt,empty_type();}inline void print(empty_type x){}inline void print(char x){putchar(x);}inline void print(char*x){for(int i=0;x[i];i++)putchar(x[i]);}inline void print(const char*x){for(int i=0;x[i];i++)putchar(x[i]);}template<typename T>Octane_t&operator>>(Octane_t&io,T&b){return io.flag=read(b),io;}Octane_t&operator>>(Octane_t&io,char*b){return io.flag=read(b),io;}template<typename T>Octane_t&operator<<(Octane_t&io,T b){return print(b),io;};
// code by pt
const int N = 4e5+7;
int read()
{
int x;
cin >> x;
return x;
}
#define pa pair<int,int>
#define fi first
#define se second
map<pa,int>mp; // 编号
map<pa,int>id;// 边的左边的面
// cnt 面的数量
int n,q,cnt;
// belong 原图中点的染色
int belong[N],num;
int head[N];bool vis[N];
vector<int>gra[N];
int f[N];
void init(int n){for(int i=1;i<=n;i++) f[i]=i;}
int find(int x){return f[x]==x ? x : f[x] = find(f[x]);}
void merge(int x,int y){
if(find(x)==find(y)) return ;
f[find(y)] = find(x);
}
bool check(int x,int y){return find(x)==find(y);}
void dfs(int x,int pre,int st)
{
if(x==st) return ;
int p = mp[{x,pre}];
p = (p+1)%gra[x].size();
int y = gra[x][p];
id[{x,y}]=cnt;
dfs(y,x,st);
}
int extend(vector<int> &v,int &st)
{
for(;st<v.size();++st)
{
int x = v[st];
for(int i=head[x];i<gra[x].size();head[x] = ++ i)
{
int y = gra[x][i];
if(!vis[y])
{
vis[y] = true;
v.emplace_back(y);
return true;
}
}
}
return false;
}
signed main()
{
n = read(); q= read();
for(int x=1;x<=n;x++)
{
int k = read();
for(int i=1;i<=k;i++)
{
int y;
cin >> y;
gra[x].emplace_back(y);
mp[{x,y}]=i-1;
}
}
for(int x=1;x<=n;x++)
{
for(auto y:gra[x])
{
if(id.count({x,y})) continue;
id[{x,y}] = ++ cnt;
dfs(y,x,x);
}
}
for(int x=1;x<=n;x++) sort(gra[x].begin(),gra[x].end());
init(cnt);
char opt;int x;int y;
int tot = 1;
int lasans = 0;
for(int i=1;i<=q;i++)
{
cin >> opt >> x >> y;
x^=lasans;
y^=lasans;
if(opt=='-')
{
// qwq
int a = id[{x,y}] , b= id[{y,x}];
gra[x].erase(lower_bound(gra[x].begin(),gra[x].end(),y));
gra[y].erase(lower_bound(gra[y].begin(),gra[y].end(),x));
if(find(a)==find(b))
{
// 出现了环
tot ++ ;
int p1=0;int p2=0;
vector<int>v1,v2;
v1.emplace_back(x);
v2.emplace_back(y);
while(1)
{
if(!extend(v1,p1)){
num ++ ;
for(auto v:v1) belong[v]=num;
break;
}
if(!extend(v2,p2)){
num ++ ;
for(auto v:v2) belong[v]=num;
break;
}
}
for(auto v:v1) head[v]=vis[v] = 0;
for(auto v:v2) head[v]=vis[v]=0;
}
else merge(a,b);
cout << (lasans = tot) << '\n';
}
else cout << (lasans = (belong[x]==belong[y])) << '\n';
}
return 0;
}
Problem C :
题面描述 :
你现在要从 \((0,0)\) 走到 \((n, m)\) , 只能向上或向右走。显然, 一个有 $ \frac{(n+m) !}{n ! m !}$ 种路径。
对于每一种路径, 假设它和两条直线 \(y=0, x=n\) 构成的封闭图形面积为 \(k\) , 那么这条路径的贡献为 $q^{k} $, 求所有路径的贡献和对 \(p\) 取模的结果
Solution:
考虑答案为 $ \frac{F(n+m)}{F(n) F(m)} $
其中 $F(n)=\prod\left(q^{i}-1\right) $
设 \(k\) 为 $ \min \left(k>0, q^{k}-1 \equiv 0 \bmod p\right) \( \) F(n)=\prod_{1 \leq i \leq \frac{n}{k}}\left(q^{i k}-1\right) \prod_{1 \leq i \leq n, k \nmid n}\left(q^{i}-1\right) $
后半部分可以快速预处理。
考虑 \(\prod_{1 \leq i \leq \frac{n}{k}}\left(q^{i k}-1\right) =\left(q^{k}-1\right)^{n / k} \prod_{i=1}^{n / k}\left(\sum_{j=0}^{i-1} q^{k j}\right) \)而 $\sum_{j=0}^{i-1} q^{k j} \equiv i \bmod p $
对于所有 \(p \mid i\) 的 $ i $\(\begin{array}{l} \prod_{i=1}^{n / k p}\left(\sum_{j=0}^{p i-1} q^{k j}\right) =\left(\sum_{j=0}^{p-1} q^{k j}\right)^{n / p k} \prod_{i=1}^{n / k p}\left(\sum_{j=0}^{i-1} q^{j k p}\right) \end{array}\)
可以递归处理。
Code:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int MOD;
ll pow_mod(ll x,int k) {
ll ans=1;
while (k) {
if (k&1) ans=ans*x%MOD;
x=x*x%MOD;
k>>=1;
}
return ans;
}
ll mid[200005],miv[200005],mi[200005];
ll facd[200005],facv[200005];
int k;
void pre(ll q) {
ll v=1;
do {
v=v*q%MOD;
k++;
} while (v!=1);
mi[0]=mid[0]=1;
for(int i=1;i<k;i++) {
mi[i]=mi[i-1]*q%MOD;
mid[i]=mid[i-1]*(mi[i]-1LL+MOD)%MOD;
}
miv[k-1]=pow_mod(mid[k-1],MOD-2);
for(int i=k-2;i>=0;i--) miv[i]=miv[i+1]*(mi[i+1]-1LL+MOD)%MOD;
facd[0]=1;
for(int i=1;i<MOD;i++) facd[i]=facd[i-1]*i%MOD;
facv[MOD-1]=pow_mod(facd[MOD-1],MOD-2);
for(int i=MOD-2;i>=0;i--) facv[i]=facv[i+1]*(i+1)%MOD;
}
ll calc1(int n,int m) {
return mid[n]*miv[m]%MOD*miv[n-m]%MOD;
}
ll calc2(int n,int m) {
return facd[n]*facv[m]%MOD*facv[n-m]%MOD;
}
ll solve(int n,int m) {
if (!m) return 1;
if (n%MOD<m%MOD) return 0;
return solve(n/MOD,m/MOD)*calc2(n%MOD,m%MOD)%MOD;
}
int main() {
int cases,q;
scanf("%d%d%d",&cases,&q,&MOD);
if (q==MOD) {
for(;cases;cases--) puts("1");
return 0;
}
pre(q);
for(;cases;cases--) {
int n,m;
scanf("%d%d",&n,&m);
if ((n+m)%k<n%k) puts("0");
else printf("%lld\n",calc1((n+m)%k,n%k)*solve((n+m)/k,n/k)%MOD);
}
return 0;
}