Codeforces Round #415 (Div. 1) 题解

73 篇文章 0 订阅
25 篇文章 0 订阅

A:

n 个互不相同的正整数,记作x1,x2,x3,,xn
A={1,2,3,,n}
aA,aF(a) mod 109+7
在这里, F(a)=maxi,ja|xixj|
1n31051xi109

Solution A:

不妨将 xi 按照升序排好
对于 F(a)=maxi,ja|xixj|
注意到这个函数其实只跟集合 a 中的最大值和最小值有关
那么就枚举所有可能的情况,乘上合法集合个数就行了
具体的,枚举xi xj 中的数字个数 k
强制xi xj 必须出现在集合中,这样合法集合个数有 2k
枚举 k 1n1,端点的值的和可以用前缀和后缀和统计

#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(1x,yn)
然后 Noora 会从他点的 k 道菜中选择两道a,b满足
|xa| |yb| 都是所有可行结果中最小的
此时,若 |xa||yb| Noora 将会回答 TAK 否则是 NIE
Leha 必须在不超过 60 次提问内确定 k 道菜中的任意两道是什么
2kn105

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,x1)或者 (x+1,n) 也能用这个策略
因为每次总是选择区间内的菜更优
不过 (1,x1) (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:

有一个 109109 的矩阵,记第 x y列的元素为 (x,y)
(x,y) 的值,定义为所有 (i,y),(x,j)(1i<x,1j<y) 元素里最小的未出现过的正整数
这里写图片描述
为了方便理解,这是前 5×5 的矩阵
现在,有 q 次询问,每次是询问一个子矩形内不超过k的数字的和
1q104,1k2109,1x1x2109,1y1y2109

Solution C:

先给出一个结论,位于 (x,y) 的数字为 ((x1)(y1))+1
证明的话,考虑将矩阵的行和列重新从 0 开始标号,然后每个位置减去1
那么现在只需要证明 (x,y) 上的数字等于 xy 就行了
可以考虑使用归纳法证明,位置 (0,0) 显然是成立的
对于位置 (x,y) ,假设所有处于它左方和上方的位置都成立
按照定义证明, (x,y) 上的数字为 xy
则所有 (i,y),(x,j)(1i<x,1j<y) 的元素包含了 [0,xy) 内的所有整数
并且这些元素没有一个等于 xy
后一条比较容易证明,因为每个 (i,y) 肯定是两两不同的
同理对于每个 (x,j) 也是,所以没有一个等于 xy
对于前一条,任取一个数 z[0,xy)
因为 z<xy ,所以,若取一个最大的 k ,满足z xy 在这一位上不同
一定是 z 这一位上为0 xy 这一位上为1
那么在这一位上,一定是 x,y 中一个为 0 另一个为1
不妨假设这个 1 来自于x
取一个 a ,为x上这一位为 0
只需要对于a,将第 k 位以后的数字调整
使得找到a0y=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
满足 1i<k,xi<xi+1 并且有办法把这些村庄都参观一遍
Leha and Noora 当然是想参观尽可能多的村庄了
所以他们想知道,这个序列最长能是多少,或者说最多参观多少村庄

Solution D:

定义状态 f[i][j] 为,对于前 i 个村庄,最早需要多少天,能参观j个村庄
如果状态非法,那就直接赋值 +
i 固定的时候,显然,f[i][j]是单调递增的
因此转移方程就可以暴力写了
f[i][j]=maxf[i1][j1]<ri(f[i1][j1]+1,li)
于是,对于 f[i] 的更新,找出最小的 x ,使得f[i1][x]li
再找出最大的 y ,使得f[i1][y]<ri
那么这一整段都可以作转移 f[i1][k1]+1f[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
保证所有节点的数字组成一个 1n 的排列
定义点对 (u,v) 的权值为 φ(auav)×d(u,v)
其中 d(u,v) u,v 两点在树上的距离
随机从树上选择两点 u,v(uv)
请问得到的权值期望是多少?输出这个值对 109+7 取模的结果即可
2n2105 注意 (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=sumphigg|x,xg,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=1n
p x的最小质因数, α p 的幂次,q=xpα
枚举 x 的每一个因数z,若 p|z ,则 cp[z]=cq[zp]p1 否则, 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)的大神额

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值