A:
有 n 个互不相同的正整数,记作
x1,x2,x3,…,xn
记 A={1,2,3,…,n}
求 ∑a⊂A,a≠∅F(a) mod 109+7
在这里, F(a)=maxi,j∈a|xi−xj|
1≤n≤3∗105,1≤xi≤109
Solution A:
不妨将 xi 按照升序排好
对于 F(a)=maxi,j∈a|xi−xj|
注意到这个函数其实只跟集合 a 中的最大值和最小值有关
那么就枚举所有可能的情况,乘上合法集合个数就行了
具体的,枚举xi 和 xj 中的数字个数 k
强制xi 和 xj 必须出现在集合中,这样合法集合个数有 2k
枚举 k 从1→n−1 ,端点的值的和可以用前缀和后缀和统计
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int maxn = 3E5 + 3;
const int mo = 1000000007;
int n,Ans,A[maxn],pre[maxn],suf[maxn];
#define Mul(x,y) (1LL * (x) * (y) % mo)
#define Add(x,y) ((x) + (y) < mo ? (x) + (y) : (x) + (y) - mo)
#define Dec(x,y) ((x) - (y) >= 0 ? (x) - (y) : (x) - (y) + mo)
int main()
{
#ifdef DMC
freopen("DMC.txt","r",stdin);
#endif
cin >> n;
for (int i = 1; i <= n; i++) scanf("%d",&A[i]);
sort(A + 1,A + n + 1);
for (int i = 1; i <= n; i++) pre[i] = Add(pre[i - 1],A[i]);
for (int i = n; i; i--) suf[i] = Add(suf[i + 1],A[i]);
for (int i = 1,tmp = 1; i < n; i++)
{
Ans = Add(Ans,Mul(tmp,Dec(suf[1 + i],pre[n - i])));
tmp = Add(tmp,tmp);
}
cout << Ans << endl;
return 0;
}
B:
这是一道交互题
Noora 和 Leha 一起去一家餐馆吃饭
这家餐馆一共有 n 种菜,而Noora 点了其中 k 道
为了打发上菜前的无聊时间,他们决定玩一个游戏
每次,Leha 提问两个数字 x,y(1≤x,y≤n)
然后 Noora 会从他点的 k 道菜中选择两道a,b 满足
|x−a| 与 |y−b| 都是所有可行结果中最小的
此时,若 |x−a|≤|y−b| , Noora 将会回答 TAK 否则是 NIE
Leha 必须在不超过 60 次提问内确定 k 道菜中的任意两道是什么
2≤k≤n≤105
Solution B:
考虑如何确定一道菜
假设区间 [l,r] 中至少存在一道菜
初始令 l=1,r=n ,则条件必定符合
每次,取 l,r 的终点记作 mid
做一次询问 (mid,mid+1)
如果回答为 TAK ,那么 [l,mid] 一定至少存在一道菜
否则 [mid+1,r] 一定至少存在一道菜
那么就能将问题递归到 [l,mid] 或者是 [mid+1,r] 解决
有个显然的性质,当问题被递归以后,选择来回答的菜,一定在区间内
因为如果选一道区间外的菜,明显就违背选择原则了
那么,这样一直递归下去,在 O(logn) 的时间就能确定第一道菜
假设这道菜的位置为 x ,那么对(1,x−1) 或者 (x+1,n) 也能用这个策略
因为每次总是选择区间内的菜更优
不过 (1,x−1) 与 (x+1,n) 中可能有一个区间根本没有菜
不过多做一次询问也就能确定了
总得询问次数不超过 O(3logn) 可以通过
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int n,k;
char s[30];
bool Query(int x,int y)
{
printf("1 %d %d\n",x,y);
fflush(stdout); scanf("%s",s);
return s[0] == 'T' ? 1 : 0;
}
int Check(int l,int r)
{
while (r - l > 1)
{
int mid = l + r >> 1;
bool ret = Query(mid,mid + 1);
if (ret) r = mid; else l = mid;
}
return Query(l,r) ? l : r;
}
int main()
{
cin >> n >> k;
int p1 = Check(1,n),p2;
if (p1 > 1)
{
p2 = Check(1,p1 - 1);
bool ret = Query(p2,p1);
if (ret)
{
printf("2 %d %d\n",p1,p2);
fflush(stdout); return 0;
}
}
p2 = Check(p1 + 1,n);
printf("2 %d %d\n",p1,p2);
fflush(stdout);
return 0;
}
C:
有一个 109∗109 的矩阵,记第 x 行
y 列的元素为 (x,y)
(x,y) 的值,定义为所有 (i,y),(x,j)(1≤i<x,1≤j<y) 元素里最小的未出现过的正整数
为了方便理解,这是前 5×5 的矩阵
现在,有 q 次询问,每次是询问一个子矩形内不超过k 的数字的和
1≤q≤104,1≤k≤2∗109,1≤x1≤x2≤109,1≤y1≤y2≤109
Solution C:
先给出一个结论,位于 (x,y) 的数字为 ((x−1)⨁(y−1))+1
证明的话,考虑将矩阵的行和列重新从 0 开始标号,然后每个位置减去1
那么现在只需要证明 (x,y) 上的数字等于 x⨁y 就行了
可以考虑使用归纳法证明,位置 (0,0) 显然是成立的
对于位置 (x,y) ,假设所有处于它左方和上方的位置都成立
按照定义证明, (x,y) 上的数字为 x⨁y
则所有 (i,y),(x,j)(1≤i<x,1≤j<y) 的元素包含了 [0,x⨁y) 内的所有整数
并且这些元素没有一个等于 x⨁y
后一条比较容易证明,因为每个 (i,y) 肯定是两两不同的
同理对于每个 (x,j) 也是,所以没有一个等于 x⨁y
对于前一条,任取一个数 z∈[0,x⨁y)
因为 z<x⨁y ,所以,若取一个最大的 k ,满足z 和 x⨁y 在这一位上不同
一定是 z 这一位上为0 而 x⨁y 这一位上为1
那么在这一位上,一定是 x,y 中一个为 0 另一个为1
不妨假设这个 1 来自于x
取一个 a ,为x 上这一位为 0
只需要对于a ,将第 k 位以后的数字调整
使得找到a0⨁y=z 就行了
这样的 a0 一定是存在的
因为第 k 位小于x ,那么这个 a0 一定小于 x
于是就证明了这个结论
那么对于子矩形求和,可以利用容斥原理拆成四个矩形的二维前缀和
这样做的好处是每个矩形的求和的坐标选择范围都是0 开始的连续一段
那么就定义状态 f[i][j][k][l] 表示 dp 到第 i 位,是否贴着x,y,k 三维,数字的和
同理定义状态 g[i][j][k][l] 类似,不过表示方案数
剩下的就是一个数位 dp 的问题了
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int T = 31;
const int N = 32;
typedef long long LL;
const LL mo = 1000000007;
int q,mi[N],f[N][8],g[N][8];
#define Mul(x,y) (1LL * (x) * (y) % mo)
#define Add(x,y) ((x) = (x) + (y) < mo ? (x) + (y) : (x) + (y) - mo)
#define Dec(x,y) ((x) = (x) - (y) >= 0 ? (x) - (y) : (x) - (y) + mo)
#define Check(a,b,c,d) (((a) & (b)) ? (((c) & (d)) ? 1 : 0) : 1)
inline int Query(int x,int y,int k)
{
memset(f,0,sizeof(f));
memset(g,0,sizeof(g));
--x; --y; --k; f[T][7] = 1;
for (int i = T; i; i--)
for (int j = 0; j < 8; j++)
{
if (!f[i][j] && !g[i][j]) continue;
int ta = Check(j,4,x,mi[i - 1]);
int tb = Check(j,2,y,mi[i - 1]);
int tc = Check(j,1,k,mi[i - 1]);
for (int a = 0; a <= ta; a++)
for (int b = 0; b <= tb; b++)
{
if ((a ^ b) > tc) continue;
int nex = j; if (a < ta) nex &= 3;
if (b < tb) nex &= 5; if ((a ^ b) < tc) nex &= 6;
Add(f[i - 1][nex],f[i][j]); Add(g[i - 1][nex],g[i][j]);
if (a ^ b) Add(g[i - 1][nex],Mul(f[i][j],mi[i - 1]));
}
}
int ret = 0;
for (int i = 0; i < 8; i++)
Add(ret,g[0][i]),Add(ret,f[0][i]);
return ret;
}
inline void Solve()
{
int r1,c1,r2,c2,k;
scanf("%d%d%d%d%d",&r1,&c1,&r2,&c2,&k);
int Ans = Query(r2,c2,k);
if (c1 > 1) Dec(Ans,Query(r2,c1 - 1,k));
if (r1 > 1) Dec(Ans,Query(r1 - 1,c2,k));
if (r1 > 1 && c1 > 1) Add(Ans,Query(r1 - 1,c1 - 1,k));
printf("%d\n",Ans);
}
int main()
{
#ifdef DMC
freopen("DMC.txt","r",stdin);
#endif
cin >> q; mi[0] = 1;
for (int i = 1; i < 31; i++) mi[i] = mi[i - 1] * 2;
while (q--) Solve();
return 0;
}
D:
Leha 和 Noora 打算去一些村庄游玩
一共有 n 个村庄,但是出于一些原因,这些村庄并不总是开放的
具体的,编号为i 的村庄,只在 [li,ri] 这几天开放参观
而他们两个人一天只能去一个村庄参观
于是,他们想要选择一个参观序列 x1,x2,x3,…,xk
满足 ∀1≤i<k,xi<xi+1 并且有办法把这些村庄都参观一遍
Leha and Noora 当然是想参观尽可能多的村庄了
所以他们想知道,这个序列最长能是多少,或者说最多参观多少村庄
Solution D:
定义状态 f[i][j] 为,对于前 i 个村庄,最早需要多少天,能参观
j 个村庄
如果状态非法,那就直接赋值 +∞
当 i 固定的时候,显然,f[i][j] 是单调递增的
因此转移方程就可以暴力写了
f[i][j]=maxf[i−1][j−1]<ri(f[i−1][j−1]+1,li)
于是,对于 f[i] 的更新,找出最小的 x ,使得f[i−1][x]≥li
再找出最大的 y ,使得f[i−1][y]<ri
那么这一整段都可以作转移 f[i−1][k−1]+1→f[i][k]
特别的, f[i][x]=li
那么就等于有两类转移,区间修改,单点插入
这些都可以用 splay 直接解决
那么,就直接 splay 优化 dp 就行了,复杂度 O(nlogn)
写的时候出了点小偏差。。忘了断裂合并操作如何保证均摊复杂度结果真被卡了一发
如果一个 splay 要在位置 x,x+1 处断开
那么就先把 x 转到根然后断裂就是了
#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
const int INF = ~0U>>1;
const int maxn = 6E5 + 666;
int n,Ans,cnt,fa[maxn],key[maxn],siz[maxn],Mark[maxn],ch[maxn][2],stk[maxn];
#define maintain(x) (siz[(x)] = 1 + siz[ch[(x)][0]] + siz[ch[(x)][1]])
inline void pushdown(int x)
{
if (!Mark[x]) return; key[x] += Mark[x];
if (ch[x][0]) Mark[ch[x][0]] += Mark[x];
if (ch[x][1]) Mark[ch[x][1]] += Mark[x]; Mark[x] = 0;
}
inline void rotate(int x)
{
int y = fa[x],z = fa[y];
int d = ch[y][0] == x ? 0 : 1;
ch[y][d] = ch[x][d^1]; if (ch[x][d^1]) fa[ch[x][d^1]] = y;
ch[x][d^1] = y; fa[y] = x; maintain(y); maintain(x);
fa[x] = z; if (z) ch[z][ch[z][1] == y] = x,maintain(z);
}
inline void splay(int x)
{
int tp = 0;
for (int z = x; z; z = fa[z]) stk[++tp] = z;
while (tp) pushdown(stk[tp--]);
for (int y = fa[x]; y; rotate(x),y = fa[x])
if (fa[y]) rotate((ch[y][0] == x) ^ (ch[fa[y]][0] == y) ? x : y);
}
inline int Lower_Bound(int x,int k)
{
if (!x) return 0; pushdown(x);
if (key[x] < k) return Lower_Bound(ch[x][1],k);
else
{
int ret = Lower_Bound(ch[x][0],k);
return ret ? ret : x;
}
}
inline int Find(int x,int k)
{
pushdown(x);
int now = siz[ch[x][0]] + 1;
if (now == k) return x;
if (now > k) return Find(ch[x][0],k);
else return Find(ch[x][1],k - now);
}
inline void Search(int x,int k)
{
if (!x) return; pushdown(x);
int now = siz[ch[x][0]] + 1;
if (key[x] != INF) Ans = max(Ans,k + now);
Search(ch[x][0],k); Search(ch[x][1],k + now);
}
inline int Build(int l,int r)
{
if (l > r) return 0;
int mid = l + r >> 1;
ch[mid][0] = Build(l,mid - 1);
ch[mid][1] = Build(mid + 1,r);
if (ch[mid][0]) fa[ch[mid][0]] = mid;
if (ch[mid][1]) fa[ch[mid][1]] = mid;
maintain(mid); return mid;
}
inline int getint()
{
char ch = getchar(); int ret = 0;
while (ch < '0' || '9' < ch) ch = getchar();
while ('0' <= ch && ch <= '9')
ret = ret * 10 + ch - '0',ch = getchar();
return ret;
}
int main()
{
#ifdef DMC
freopen("DMC.txt","r",stdin);
#endif
n = getint();
for (int i = 2; i <= n + 2; i++) key[i] = INF;
Build(1,n + 2); cnt = n + 2;
for (int i = 1; i <= n; i++)
{
int l = getint(),r = getint(); splay(1);
int x = Lower_Bound(1,l),y = Lower_Bound(1,r);
if (x == y) splay(x),key[x] = l;
else
{
splay(x); int rkx = siz[ch[x][0]] + 1;
splay(1); int A = Find(1,rkx - 1);
splay(A); fa[ch[A][1]] = 0; ch[A][1] = 0; maintain(A);
splay(y); fa[ch[y][0]] = 0; ch[y][0] = 0;
int B = ch[y][1]; fa[B] = 0; ch[y][1] = 0;
splay(x); ++Mark[x]; fa[x] = ++cnt;
key[cnt] = l; ch[cnt][1] = x; maintain(cnt);
int C = Find(cnt,siz[cnt]); splay(C);
ch[A][1] = C; fa[C] = A; maintain(A);
ch[C][1] = B; fa[B] = C; maintain(C);
}
}
splay(1); Search(1,0); cout << Ans - 1 << endl;
return 0;
}
E:
有一棵有
n 个节点的树,第 i 个节点上写着数字ai
保证所有节点的数字组成一个 1→n 的排列
定义点对 (u,v) 的权值为 φ(auav)×d(u,v)
其中 d(u,v) 为 u,v 两点在树上的距离
随机从树上选择两点 u,v(u≠v)
请问得到的权值期望是多少?输出这个值对 109+7 取模的结果即可
2≤n≤2∗105 注意 (u,v) 与 (v,u) 算作不同点对
Solution E:
首先对于 φ(auav) 不太好求
注意到 φ(auav)=φ(au)φ(av)gφ(g),g=gcd(au,av)
证明的话,直接用 φ 的展开式就行了
对这棵树进行点分治,每次取出重心,记为 o
φ(auav)×d(u,v)=φ(au)φ(av)gφ(g)(Lu+Lv)=φ(av)gφ(g)φ(au)Lu+φ(au)gφ(g)φ(av)Lv
其中 Lu 为 u 到o 的距离
点分治的时候只需要考虑每一层跨过重心的路径
所以对于每个点 u 只需要知道其它子树中∑φ(av)gφ(g) 的值就行了
记 sumg=∑gcd(au,av)=gφ(av)
于是 ∑φ(av)gφ(g)=∑g|ausumggφ(g)
那么最后的问题是 sumg 的求法了
记 sumphig=∑g|avφ(av)
显然有 sumg=sumphig−∑g|x,x≠g,x|ausumx
记 f(n) 为 n 的约数的个数
可以用积分证明,此时的复杂度约为O((∑ni=1nif(i))logn)≈O(nlog3n)
最后的瓶颈是 sumg 的求法太慢了
注意到 sumg 总是能写成 sumphig 的和
那么如果能找到一组系数,使得 ∑g|ausumggφ(g)=∑g|aucau[g]sumphig
原问题就能在 O(nlog2n) 的时间解决了
对于 c 数组的计算,枚举x=1→n
记 p 为x 的最小质因数, α 为 p 的幂次,q=xpα
枚举 x 的每一个因数z ,若 p|z ,则 cp[z]=cq[zp]p−1 否则, cp[z]=cq[z]
边界显然, c1[1]=1
公式的证明可以采用数学归纳法
#include<iostream>
#include<cstdio>
#include<vector>
#include<stack>
#include<queue>
#include<cstring>
#include<algorithm>
//#include<ctime>
using namespace std;
typedef long long LL;
const int maxn = 2E5 + 5;
const LL mo = 1000000007;
int n,m,O,Max,cnt,tot,Cnt,phi[maxn],A[maxn],Fac[maxn],mu[maxn],pri[maxn]
,inq[maxn],dis[maxn],Inv[maxn],siz[maxn],sumphi[maxn],vis[maxn];
bool not_pri[maxn],Huge[maxn]; LL Ans;
queue <int> Q; stack <int> stk;
vector <int> v[maxn],d[maxn],c[maxn];
#define Mul(x,y) (1LL * (x) * (y) % mo)
#define max(a,b) ((a) > (b) ? (a) : (b))
#define Add(x,y) ((x) + (y) < mo ? (x) + (y) : (x) + (y) - mo)
inline int getint()
{
char ch = getchar(); int ret = 0;
while (ch < '0' || '9' < ch) ch = getchar();
while ('0' <= ch && ch <= '9')
ret = ret * 10 + ch - '0',ch = getchar();
return ret;
}
int ksm(int x,int y)
{
int ret = 1;
for (; y; y >>= 1)
{
if (y & 1) ret = Mul(ret,x);
x = Mul(x,x);
}
return ret;
}
inline void Clear()
{
while (!stk.empty())
sumphi[stk.top()] = 0,stk.pop();
}
inline void Insert(int x)
{
int y = A[x];
for (int i = 0; i < d[y].size(); i++)
{
int now = d[y][i];
sumphi[now] = Add(sumphi[now],phi[y]);
if (vis[now] != cnt) vis[now] = cnt,stk.push(now);
}
}
inline void Dfs2(int x,int tot,int from)
{
int ma = 0; siz[x] = 1;
for (int i = 0; i < v[x].size(); i++)
{
int to = v[x][i];
if (to == from || Huge[to]) continue;
Dfs2(to,tot,x); siz[x] += siz[to];
ma = max(ma,siz[to]);
}
ma = max(ma,tot - siz[x]);
if (ma < Max) Max = ma,O = x;
}
inline void BFS1(int x)
{
dis[x] = 1; inq[x] = ++Cnt; Q.push(x);
while (!Q.empty())
{
int k = Q.front(),y = A[k]; Q.pop(); LL sum = 0;
for (int i = 0; i < d[y].size(); i++)
sum += 1LL * c[y][i] * sumphi[d[y][i]];
sum %= mo; Ans += Mul(sum,Mul(phi[y],dis[k]));
for (int i = 0; i < v[k].size(); i++)
{
int to = v[k][i];
if (inq[to] == Cnt || Huge[to]) continue;
inq[to] = Cnt; dis[to] = dis[k] + 1; Q.push(to);
}
}
Ans %= mo;
}
inline int BFS2(int x)
{
int ret = 0; Q.push(x); inq[x] = ++Cnt;
while (!Q.empty())
{
int k = Q.front(); Q.pop(); ++ret; Insert(k);
for (int i = 0; i < v[k].size(); i++)
{
int to = v[k][i];
if (inq[to] == Cnt || Huge[to]) continue;
inq[to] = Cnt; Q.push(to);
}
}
return ret;
}
inline void Dfs1(int x,int tot)
{
Max = maxn; Dfs2(x,tot,0); int o = O;
Huge[o] = 1; ++cnt; Insert(o);
for (int i = 0; i < v[o].size(); i++)
{
int to = v[o][i];
if (Huge[to]) continue;
BFS1(to); BFS2(to);
}
Clear(); ++cnt;
for (int i = v[o].size() - 1; i >= 0; i--)
{
int to = v[o][i];
if (Huge[to]) continue;
BFS1(to); siz[to] = BFS2(to);
}
Clear();
for (int i = 0; i < v[o].size(); i++)
{
int to = v[o][i];
if (Huge[to]) continue;
Dfs1(to,siz[to]);
}
}
void Pre_Work()
{
n = getint(); Fac[0] = 1;
for (int i = 1; i <= n; i++) A[i] = getint(),m = max(m,A[i]);
for (int i = 1; i <= m; i++) Fac[i] = Mul(Fac[i - 1],i);
Inv[m] = ksm(Fac[m],mo - 2);
for (int i = m - 1; i; i--) Inv[i] = Mul(Inv[i + 1],i + 1);
for (int i = m; i; i--) Inv[i] = Mul(Inv[i],Fac[i - 1]);
for (int i = 1; i < n; i++)
{
int x = getint(),y = getint();
v[x].push_back(y); v[y].push_back(x);
}
phi[1] = mu[1] = 1;
for (int i = 2; i <= m; i++)
{
if (!not_pri[i])
pri[++tot] = i,phi[i] = i - 1,mu[i] = -1;
for (int j = 1; j <= tot; j++)
{
int Nex = i * pri[j];
if (Nex > n) break; not_pri[Nex] = 1;
if (i % pri[j] == 0)
{
phi[Nex] = phi[i] * pri[j]; mu[Nex] = 0; break;
}
mu[Nex] = -mu[i]; phi[Nex] = phi[i] * (pri[j] - 1);
}
}
for (int i = 1; i <= m; i++) d[i].push_back(1);
for (int i = 2; i <= m; i++)
{
if (!mu[i]) continue;
for (int j = i; j <= m; j += i) d[j].push_back(i);
}
c[1].push_back(1);
for (int i = 2; i <= m; i++)
{
int q = i,p = d[i][1],p1 = 0,p3 = 0;
while (q % p == 0) q /= p;
for (int j = 0; j < d[i].size(); j++)
{
int z = d[i][j];
if (z % p != 0)
{
while (d[q][p1] != z) ++p1;
c[i].push_back(c[q][p1]);
}
else
{
int now = z / p,tmp;
while (d[q][p3] != now) ++p3;
tmp = Mul(Inv[p - 1],c[q][p3]);
c[i].push_back(tmp);
}
}
}
}
int main()
{
/*#ifdef DMC
freopen("DMC.txt","r",stdin);
#else
freopen("sm.in","r",stdin);
freopen("sm.out","w",stdout);
#endif*/
Pre_Work(); Dfs1(1,n);
cout << Mul(Mul(2,Ans),Mul(Inv[n],Inv[n - 1])) << endl;
//cerr << (double)(clock()) / CLOCKS_PER_SEC << endl;
return 0;
}
具体的复杂度与公式证明限于篇幅就不介绍了
(主要是我写过一遍,太懒不想再写了)
有兴趣的朋友欢迎向我发邮件索要。。。就是 QQ 邮箱啦
关于 E 题,发现了一个使用虚树做法O(nlogn) 的大神额