2018 多校第五场部分题解

链接:2018 Multi-University Training Contest 5
过题数:1
排名:470/704

B. Beautiful Now

题意

给定一个没有前导零的整数 n n ,其十进制表示为 (x1x2xm)10 ( x 1 x 2 ⋯ x m ) 10 ,即 n=mi=110mixi n = ∑ i = 1 m 10 m − i x i ,可以对这个整数进行 k k 次操作,每次操作选择两个整数 i,j (1ijm,xj0) i , j   ( 1 ≤ i ≤ j ≤ m , x j ≠ 0 ) 并将这两个位置上的数字交换,问最终能够交换得到的最小的数字和最大的数字分别是多少。

输入

第一行为一个整数 T (1T100) T   ( 1 ≤ T ≤ 100 ) ,接下去 T T 行每行两个整数 n,k (1n,k109) n , k   ( 1 ≤ n , k ≤ 10 9 )

输出

每组数据输出经过 k k 次合法交换后能够得到的最大值和最小值。

样例

输入
5
12 1
213 2
998244353 1
998244353 2
998244353 3
输出
12 21
123 321
298944353 998544323
238944359 998544332
233944859 998544332

题解

由于每次交换可以选择 1ijm 1 ≤ i ≤ j ≤ m ,其中 i i 可以等于 j j ,因此答案可以从任意小于等于 k k 次的合法交换中取最小值得到,题目要求在交换过程中不允许出现前导零,可以证明,如果交换的结果不含有前导零,就一定存在一种方式在不出现前导零的情况下从原数字得到结果数字。最后就是暴力枚举所有交换次数小于等于 k k 的全排列,每次 O(n) O ( n ) 生成全排列会超时,可以预处理 9 9 以内的全排列(如果长度达到 10 10 就只有一种答案),全排列的交换次数可以用并查集找环计算。如果 k k 大于等于 m1 m − 1 直接生成不含前导零的最小字典序和最大字典序作为答案。

过题代码

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;

#define LL long long
const int maxn = 20;
int T, k, len;
char n[maxn], Max[maxn], Min[maxn], tmp[maxn];
int fa[maxn], per[maxn], ten[maxn];
vector<int> G[maxn][maxn];

void Init(int len) {
    for(int i = 0; i < len; ++i) {
        fa[i] = i;
    }
}

int Find(int x) {
    return x == fa[x]? x: fa[x] = Find(fa[x]);
}

void unit(int x, int y) {
    int xx = Find(x);
    int yy = Find(y);
    fa[xx] = yy;
}

int Count(int *per, int len) {
    int cnt = len;
    Init(len);
    for(int i = 0; i < len; ++i) {
        if(Find(per[i]) != Find(i)) {
            unit(per[i], i);
            --cnt;
        }
    }
    return len - cnt;
}

void Init() {
    ten[0] = 1;
    for(int i = 1; i <= 9; ++i) {
        ten[i] = ten[i - 1] * 10;
    }
    for(int len = 0; len <= 9; ++len) {
        for(int i = 0; i < len; ++i) {
            per[i] = i;
        }
        do {
            int cnt = Count(per, len);
            int num = 0;
            for(int i = 0; i < len; ++i) {
                num += per[i] * ten[i];
            }
            G[len][cnt].push_back(num);
        } while(next_permutation(per, per + len));
    }
}

int main() {
    #ifdef LOCAL
        freopen("test.txt", "r", stdin);
//    freopen("out.txt", "w", stdout);
    #endif // LOCAL
    ios::sync_with_stdio(false);

    Init();
    scanf("%d", &T);
    while(T--) {
        scanf("%s%d", n, &k);
        len = strlen(n);
        if(len == 10) {
            printf("%s %s\n", n, n);
            continue;
        }
        strcpy(Min, n);
        strcpy(Max, n);
        if(k >= len - 1) {
            sort(Min, Min + len);
            sort(Max, Max + len);
            int Index = 0;
            for(int i = 0; i < len; ++i) {
                if(Min[i] != '0') {
                    Index = i;
                    break;
                }
            }
            swap(Min[0], Min[Index]);
            for(int i = 0; i < len / 2; ++i) {
                swap(Max[i], Max[len - i - 1]);
            }
            printf("%s %s\n", Min, Max);
            continue;
        }
        for(int i = 1; i <= k; ++i) {
            int llen = G[len][i].size();
            for(int j = 0; j < llen; ++j) {
                int num = G[len][i][j];
                if(n[num % 10] == '0') {
                    continue;
                }
                for(int k = 0; k < len; ++k) {
                    tmp[k] = n[num / ten[k] % 10];
                }
                tmp[len] = '\0';
                if(strcmp(Min, tmp) > 0) {
                    strcpy(Min, tmp);
                } else if(strcmp(Max, tmp) < 0) {
                    strcpy(Max, tmp);
                }
            }
        }
        printf("%s %s\n", Min, Max);
    }

    return 0;
}

E. Everything Has Changed

题意

在平面直角坐标系内有一个圆心在原点,半径为 R R 的初始圆,以及 m m 个圆心在 (xi,yi) ( x i , y i ) ,半径为 ri r i 的圆,这 m m 个圆两两互不相交且不会包含整个初始圆,这 m m 个小圆将对初始圆进行切割,问最终初始圆的周长。

输入

第一行为一个整数 T (1T1000) T   ( 1 ≤ T ≤ 1000 ) ,接下去有 T T 组数据,每组数据第一行为两个整数 m,R (1m100) m , R   ( 1 ≤ m ≤ 100 ) ,接下去 m m 行每行三个整数 xi,yi,ri (1000xi,yi1000,1R,ri1000) x i , y i , r i   ( − 1000 ≤ x i , y i ≤ 1000 , 1 ≤ R , r i ≤ 1000 )

输出

输出最终圆的周长,误差在 106 10 − 6 以内都认为答案正确。

样例

输入
1
4 10
6 3 5
10 -4 3
-2 -4 4
0 9 1
输出
81.62198908430238475376
提示
切割圆的方式如下:

2018 多校第五场部分题解_第1张图片

其中红线为圆的周长。

题解

对于两圆相离或者外切的情况,对周长的贡献都为 0 0 ,对于两圆内含的情况,贡献也为零,因此只要考虑两圆相交与内切的情况,先考虑第一种相交(两圆圆心在交点连线的两侧):



AG=h,O1O2=d,O1G=a,O2G=b A G = h , O 1 O 2 = d , O 1 G = a , O 2 G = b ,则可以联立方程:

a2+h2=r21b2+h2=r22a+b=d { a 2 + h 2 = r 1 2 b 2 + h 2 = r 2 2 a + b = d
解方程得到 b=r22r21+d22d b = r 2 2 − r 1 2 + d 2 2 d ,而对于两圆圆心在交点连线同侧的情况:



只需要将上面方程组的第 3 3 个方程改为 ab=d a − b = d 即可,得到 b=r22r21+d22d b = − r 2 2 − r 1 2 + d 2 2 d ,发现和上式差一个负号,要求圆 O2 O 2 的贡献,就是求圆 O2 O 2 在圆 O1 O 1 内部的圆弧长度,因此如果直接对 br2 b r 2 arccos arccos ,第二种情况的负号正好使得圆 O2 O 2 的圆心角计算得到大于 90° 90 ° 的部分。
最后还需要减掉圆 O1 O 1 在圆 O2 O 2 内部的圆弧长度,计算方式与第一种情况相同。

过题代码

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;

#define LL long long
const double eps = 1e-8;
const double PI = acos(-1.0);
const int maxn = 100 + 100;
struct Circle {
    double x, y;
    double r, val;
};
int  T, n;
double R;
Circle c[maxn];

int sign(const double &x) {
    if(fabs(x) < eps) {
        return 0;
    }
    if(x < 0) {
        return -1;
    }
    return 1;
}

void get_val(Circle &c, int Index) {
    double dis = c.x * c.x + c.y * c.y;
    int sgn = sign(dis - (R + c.r) * (R + c.r));
    if(sgn > 0) {
        c.val = 0;
        return ;
    }
    sgn = (R - c.r) * (R - c.r) - dis;
    if(sgn > 0) {
        c.val = 0;
        return ;
    }
    double a = (c.r * c.r - R * R + dis) / (2 * sqrt(dis));
    double theta = acos(a / c.r) * 2;
    c.val = c.r * theta;
    double b = (R * R - c.r * c.r + dis) / (2 * sqrt(dis));
    theta = acos(b / R) * 2;
    c.val -= R * theta;
}

bool Set(Circle &a, Circle &b) {
    double dis = (a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y);
    int sgn = sign(dis - (a.r + b.r) * (a.r + b.r));
    if(sgn > 0) {
        return false;
    }
    sgn = (a.r - b.r) * (a.r - b.r) - dis;
    if(sgn > 0) {
        return false;
    }
    return true;
}

int main() {
    #ifdef LOCAL
        freopen("test.txt", "r", stdin);
//    freopen("out.txt", "w", stdout);
    #endif // LOCAL
    ios::sync_with_stdio(false);

    scanf("%d", &T);
    while(T--) {
        scanf("%d%lf", &n, &R);
        for(int i = 1; i <= n; ++i) {
            scanf("%lf%lf%lf", &c[i].x, &c[i].y, &c[i].r);
            get_val(c[i], i);
        }
        double ans = 2 * PI * R;
        for(int i = 1; i <= n; ++i) {
            ans += c[i].val;
        }
        printf("%.10f\n", ans);
    }

    return 0;
}

G. Glad You Came

题意

有一个长度为 n n 的序列,序列中每个数字的初始值都为 0 0 ,接下来对这个序列进行 m m 次操作,每次操作将区间 [li,ri] [ l i , r i ] 之间的所有数字 ai a i ,都更新为 max(ai,vi) max ( a i , v i ) ,输出 m m 次操作后 ni1i×ai ⨁ i − 1 n i × a i 的值。为了避免大数据输入,给出初始随机种子 X,Y,Z X , Y , Z ,每次随机数字由以下伪代码生成:

2018 多校第五场部分题解_第2张图片

总共生成 3m 3 m 个随机数 f1,f2,,f3m f 1 , f 2 , ⋯ , f 3 m ,第 i i 次操作的 li,ri,vi l i , r i , v i 由以下规则得到:

lirivi=min((f3i2%n)+1,(f3i1%n)+1)=max((f3i2%n)+1,(f3i1%n)+1)=f3i%230(1)(2)(3) (1) l i = min ( ( f 3 i − 2 % n ) + 1 , ( f 3 i − 1 % n ) + 1 ) (2) r i = max ( ( f 3 i − 2 % n ) + 1 , ( f 3 i − 1 % n ) + 1 ) (3) v i = f 3 i % 2 30

输入

第一行为一个整数 T (1T100) T   ( 1 ≤ T ≤ 100 ) ,接下去 T T 行每行 5 5 个整数 n,m,X,Y,Z (1n105,1m5×106,0X,Y,Z<230) n , m , X , Y , Z   ( 1 ≤ n ≤ 10 5 , 1 ≤ m ≤ 5 × 10 6 , 0 ≤ X , Y , Z < 2 30 )

输出

对于每组数据,输出最终结果。

样例

输入
4
1 10 100 1000 10000
10 100 1000 10000 100000
100 1000 10000 100000 1000000
1000 10000 100000 1000000 10000000
输出
1031463378
1446334207
351511856
47320301347
提示
第一组数据经过 10 10 次操作序列元素为 [1031463378] [ 1031463378 ]
第二组数据经过 100 100 次操作后数组元素为 [1036205629,1064909195,1044643689,1062944339,1062944339,1062944339,1062944339,1057472915,1057472915,1030626924] [ 1036205629 , 1064909195 , 1044643689 , 1062944339 , 1062944339 , 1062944339 , 1062944339 , 1057472915 , 1057472915 , 1030626924 ]

题解

反向用 ST S T O(1) O ( 1 ) 地标记最大值更新,最后从大到小将最大值标记往下打,就可以得到答案,时间复杂度为 O(m+nlogn) O ( m + n log ⁡ n )

过题代码

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;

#define LL long long
const int maxn = 100000 + 100;
const int Log = 20;
const unsigned int one = (unsigned int)1 << 30;
int T, n, m;
unsigned int x, y, z, l, r, v;
unsigned int f1, f2, f3;
LL stmax[maxn][Log], mn[maxn];

void Init() {
    mn[0] = -1;
    for(int i = 1; i <= n; ++i) {
        mn[i] = ((i & (i - 1)) == 0)? mn[i - 1] + 1: mn[i - 1];
        memset(stmax[i], 0, sizeof(stmax[i]));
    }
}

unsigned int Rand(unsigned int &x, unsigned int &y, unsigned int &z) {
    x = x ^ (x << 11);
    x = x ^ (x >> 4);
    x = x ^ (x << 5);
    x = x ^ (x >> 14);
    unsigned int w = x ^ (y ^ z);
    x = y;
    y = z;
    z = w;
    return z;
}

int main() {
    #ifdef LOCAL
        freopen("test.txt", "r", stdin);
//    freopen("out.txt", "w", stdout);
    #endif // LOCAL
    ios::sync_with_stdio(false);

    cin >> T;
    while(T--) {
        cin >> n >> m >> x >> y >> z;
        Init();
        for(int i = 1; i <= m; ++i) {
            f1 = Rand(x, y, z);
            f2 = Rand(x, y, z);
            f3 = Rand(x, y, z);
            f1 = f1 % (unsigned int)n + 1;
            f2 = f2 % (unsigned int)n + 1;
            l = min(f1, f2);
            r = max(f1, f2);
            v = f3 % one;
            int k = mn[r - l + 1];
            stmax[l][k] = max(stmax[l][k], (LL)v);
            stmax[r - (1 << k) + 1][k] = max(stmax[r - (1 << k) + 1][k], (LL)v);
        }
        for(int j = Log - 1; j > 0; --j) {
            for(int i = 1; i + (1 << (j - 1)) <= n; ++i) {
                stmax[i][j - 1] = max(stmax[i][j - 1], stmax[i][j]);
                stmax[i + (1 << (j - 1))][j - 1] = max(stmax[i + (1 << (j - 1))][j - 1], stmax[i][j]);
            }
        }
        LL ans = 0;
        for(int i = 1; i <= n; ++i) {
            ans ^= (LL)i * stmax[i][0];
        }
        cout << ans << endl;
    }

    return 0;
}

H. Hills And Valleys

题意

给定一个长度为 n n 的序列,可以选择序列上的一个区间 [l,r] (1lrn) [ l , r ]   ( 1 ≤ l ≤ r ≤ n ) ,将这个区间上的所有数字翻转,求进行一次操作后能够得到最长非递减子序列的长度。

输入

第一行为一个整数 T (1T100) T   ( 1 ≤ T ≤ 100 ) ,接下去有 T T 组数据,每组数据第一行为一个整数 n (1n105) n   ( 1 ≤ n ≤ 10 5 ) ,第二行包含一个长度为 n n 的数字字符串 A1A2An (0Ai9) A 1 A 2 ⋯ A n   ( 0 ≤ A i ≤ 9 )

输出

输出经过一次交换后能够得到的最长非递减子序列的长度,以及翻转区间的左右端点。

样例

输入
2
9
864852302
9
203258468
输出
5 1 8
6 1 2
提示
第一组数据将 864852302 864852302 区间 [1,8] [ 1 , 8 ] 内的数字翻转后结果为 032584682 032584682 ,其最长非递减子序列长度 03588 03588
第二组数据将 203258468 203258468 区间 [1,2] [ 1 , 2 ] 内的数字翻转后结果为 023258468 023258468 ,其最长非递减子序列长度 023588 023588

题解

假设将最长非递减子序列中的每段连续相同数字都缩成一个数字,如: 0123456789 0123456789 ,则问题可以转化为求原序列与这个序列的最长“公共”子序列,这个序列中的每个数字可以匹配零次或多次,这个问题可以用 dp d p 来解决,状态定义为 dp[i][j] d p [ i ] [ j ] ,表示第一个序列前 i i 位与第二个序列前 j j 位的最大匹配长度,时间复杂度为 O(nm) O ( n m ) m m 为第二个序列的长度。
如果原序列翻转最长非递减子序列的区间为 [l,r] [ l , r ] ,则第二个序列应构造为 012(l1)lr(r1)(r2)(l+1)lr(r+1)89 012 ⋯ ( l − 1 ) l r ( r − 1 ) ( r − 2 ) ⋯ ( l + 1 ) l r ( r + 1 ) ⋯ 89 ,如翻转区间为 [3,7] [ 3 , 7 ] ,就要构造 012376543789 0123 76543 789 ,再与第一个序列进行最长“公共”子序列匹配, 7 7 之前的 3 3 是因为如果翻转的子序列区间为 [3,7] [ 3 , 7 ] ,那么在第一个 7 7 之前的那部分 3 3 在翻转后也对最长非递减子序列有贡献,如果直接忽略这个 3 3 ,那部分贡献就没有算上去,会导致漏算。
最后输出翻转的区间,只要将翻转区间跟着 dp d p 一起更新即可,用 dpl d p l dpr d p r 表示 dp d p 取得最大值时翻转的区间,其中 dpl d p l 在多个 dp d p 取最大值时应取最靠前的位置, dpr d p r 只要跟着更新,就可以得到最后一个翻转的位置。

过题代码

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;

#define LL long long
const int maxn = 100000 + 100;
int T, n, ans, ansl, ansr;
char str[maxn], stmp[20];
int dp[maxn][20], dpl[maxn][20], dpr[maxn][20];

int solve(int l, int r, int len) {
    for(int i = 1; i <= n; ++i) {
        for(int j = 1; j <= len; ++j) {
            dp[i][j] = dp[i - 1][j];
            dpl[i][j] = dpl[i - 1][j];
            dpr[i][j] = dpr[i - 1][j];
            if(str[i] == stmp[j]) {
                ++dp[i][j];
                if(j == l && dpl[i][j] == 0) {
                    dpl[i][j] = i;
                }
                if(j == r) {
                    dpr[i][j] = i;
                }
            }
            if(dp[i][j] < dp[i][j - 1]) {
                dp[i][j] = dp[i][j - 1];
                dpl[i][j] = dpl[i][j - 1];
                dpr[i][j] = dpr[i][j - 1];
            }
        }
    }
    return dp[n][len];
}

void Create(int l, int r) {
    int Index = 1;
    for(int i = 0; i <= l; ++i) {
        stmp[Index++] = i + '0';
    }
    for(int i = r; i >= l; --i) {
        stmp[Index++] = i + '0';
    }
    for(int i = r; i < 10; ++i) {
        stmp[Index++] = i + '0';
    }
}

int main() {
    #ifdef LOCAL
    freopen("test.txt", "r", stdin);
//    freopen("testout.txt", "w", stdout);
    #endif // LOCAL
    ios::sync_with_stdio(false);

    scanf("%d", &T);
    while(T--) {
        scanf("%d%s", &n, str + 1);
        for(int i = 0; i < 10; ++i) {
            stmp[i + 1] = i + '0';
        }
        ans = solve(0, 0, 10);
        ansl = ansr = 1;
        for(int i = 0; i < 10; ++i) {
            for(int j = i + 1; j < 10; ++j) {
                Create(i, j);
                int tmp = solve(i + 2, j + 2, 12);
                if(tmp > ans && dpl[n][12] != 0 && dpr[n][12] != 0) {
                    ans = tmp;
                    ansl = dpl[n][12];
                    ansr = dpr[n][12];
                }
            }
        }
        printf("%d %d %d\n", ans, ansl, ansr);
    }

    return 0;
}

你可能感兴趣的:(多校)