2021“MINIEYE杯”中国大学生算法设计超级联赛(8)

2021“MINIEYE杯”中国大学生算法设计超级联赛(8)

1003. Ink on paper

  • 题意

滴墨水在纸上,墨水以每秒\(0.5\)向四面八方感染, 求多久后所有的墨水都能连接起来。

  • 思路

\(prim\)求一遍最小生成树即可,套板子0.0。

code :

int n, m;
int g[N][N], dist[N];
//邻接矩阵存储所有边
//dist存储其他点到S的距离
bool st[N];
int x[N], y[N];
int get_(int a,int b) {
    return (x[a] - x[b]) * (x[a] - x[b]) + (y[a] - y[b]) * (y[a] - y[b]);
}

int prim() {
    //如果图不连通返回INF, 否则返回res
    memset(dist, INF, sizeof dist);
    int res = 0;
    memset(st,0,sizeof st);
    for(int i = 0; i < n; i++) {
        int t = -1;
        for(int j = 1; j <= n; j++) 
            if(!st[j] && (t == -1 || dist[t] > dist[j]))
                t = j;
        //寻找离集合S最近的点        
        if(i && dist[t] == INF) return INF;
        //判断是否连通,有无最小生成树

        if(i) res = max(dist[t], res);
        st[t] = true;
        //更新最新S的权值和

        for(int j = 1; j <= n; j++) dist[j] = min(dist[j], g[t][j]);
    }

    return res;
}

void solve() {
    cin >> n;
    int u, v, w;
    for(int i = 1;i <= n;i ++) cin >> x[i] >> y[i];
    for(int i = 1; i <= n; i++)
        for(int j = i + 1; j <= n; j++){
            g[i][j] = g[j][i] = get_(i,j);
        }
    int t = prim();
    cout << t << endl;
}

1004. Counting Stars

  • 题意

有一堆星星,长度为\(n\)的序列\(a_i\)记录了初始值,然后定义两种操作。

  1. \(\forall i \in [l,r]\), 所有的星星减少\(a_i \& (-a_i)\)
  2. \(\forall i \in [l,r]\), \(a_i \neq\),所有的星星增加\(2^k\)\(2^k <= a_i <= 2^{k + 1}\)
  • 思路

操作\(1\)就是二进制中最低位从\(1 \to 0\),操作 \(2\)就是二进制中最高位往后移一位。
那么我们可以发现,最高位的变化和一些低位的变化是毫无关联的,那么我们怎么做到修改呢?
首先最高位增加很简单,存所有的最高位每次\(*\ 2\)即可,然低位怎么考虑,我们可以发现低位的数值最多能降多少次?30次,最多30次后就和低位没关系了,发现这个性质就可以考虑低位直接单点修改即可,最总总复杂度最多\(*\ 30\),然后就码线段树就行了,注意处理降到 \(0\)的情况。

code :

const int mod = 998244353;
const int N = 100100;
#define mid (l + r >> 1)
#define lsn (u << 1)
#define rsn (u << 1 | 1)
int n;
int sum1[N << 2], sum2[N << 2], laz[N << 2], zero[N << 2];
int a[N];

void up(int u) {
    sum1[u] = (sum1[lsn] + sum1[rsn]) % mod;
    sum2[u] = (sum2[lsn] + sum2[rsn]) % mod;
    zero[u] = zero[lsn] & zero[rsn];
}

void cover1(int u, int la) {
    laz[u] = laz[u] * la % mod;
    sum1[u] = sum1[u] * la % mod;
}

void down(int u) {
    cover1(lsn,laz[u]);
    cover1(rsn,laz[u]);
    zero[lsn] |= zero[u];
    zero[rsn] |= zero[u];
    if(zero[lsn]) sum2[lsn] = 0;
    if(zero[rsn]) sum2[rsn] = 0;
    laz[u] = 1;
}
void build(int u = 1,int l = 1,int r = n) {
    laz[u] = 1, zero[u] = 0;
    if(l == r) {
        for(int i = 30;i >= 0;i --) {
            int j = a[l] >> i & 1;
            if(j) {
                sum1[u] = 1 << i;
                sum2[u] = a[l] - (1 << i);
                break;
            }
        }
        return;
    }
    build(lsn,l,mid);
    build(rsn,mid + 1,r);
    up(u);
}

void update1(int L,int R,int u = 1,int l = 1,int r = n) {
    if(l == r) {
        if(sum2[u]) {
            sum2[u] -= lowbit(sum2[u]);
        }else {
            sum1[u] = 0;
            zero[u] = 1;
        }
        return;
    }
    down(u);
    if(L <= mid && !zero[lsn]) update1(L,R,lsn,l,mid);
    if(R > mid && !zero[rsn]) update1(L,R,rsn,mid + 1,r);
    up(u);
}

void update2(int L,int R,int u = 1,int l = 1,int r = n) {
    if(L <= l && R >= r) {
        sum1[u] = sum1[u] * 2 % mod;
        laz[u] = laz[u] * 2 % mod;
        return;
    }
    down(u);
    if(L <= mid) update2(L,R,lsn,l,mid);
    if(R > mid) update2(L,R,rsn,mid + 1,r);
    up(u);
}

int query(int L,int R,int u = 1,int l = 1,int r = n) {
    if(L <= l && R >= r) {
        return (sum1[u] + sum2[u]) % mod;
    }
    down(u);
    int res = 0;
    if(L <= mid && !zero[lsn]) res += query(L,R,lsn,l,mid) % mod;
    if(R > mid && !zero[rsn]) res += query(L,R,rsn,mid + 1,r) % mod;
    return res % mod;
}

void solve(){
    cin >> n;
    for(int i = 1;i <= n;i ++) cin >> a[i];
    build();
    int m;
    cin >> m;
    for(int i = 1;i <= m;i ++) {
        int op,l,r;
        cin >> op >> l >> r;
        if(op == 1) {
            cout << query(l,r) << endl;
        }else if(op == 2) {
            update1(l,r);
        }else if(op == 3) {
            update2(l,r);
        }
    }
}

1005. Separated Number

  • 题意

给你一个\(k\)\(n\), (\(1<= k <= n <= 10^{10^6}\)),求最多将\(n\)分成\(k\)的部分(允许前导零)的所有解(\((11)(451)(4) = 11 + 451 + 4 = 466\))的总和是多少。

  • 思路

计算每一位数字的贡献,如当前是第\(i\)位数字,将\(i \to i + j\)括起来,那么第\(i\)位数字\(d\)的当前贡献为\(d * 10^j\)在乘上\(i\)的前面随意取的区间个数 + \(i + j\)的后面随意取的区间个数 \(= \sum_{b =0}^{k-2} C(\frac{b}{n - j - 2})\),最后注意\(i + j = n\)的情况(只取前面的随意取的区间个数) , 然后预处理出所有的\(\sum_{b=0}^{k-1} C(\frac{b}{a}) and \sum_{b=0}^{k-2} C(\frac{b}{a})\)即可。

注意\(2\sum_{b = 0}^tC(\frac{b}{a - 1}) - C(\frac{b}{a - 1}) = \sum_{b = 0}^tC(\frac{b}{a})\)

code :

int fact[N], infact[N], power[N];
void init(int n) {
	fact[0] = 1;
    for (int i = 1 ; i <= n; ++i) {
        fact[i] = 1LL * fact[i - 1] * i % mod;
    }
    infact[n] = pow_mod(fact[n], mod - 2);
    for(int i = n - 1;i >= 0;i --) infact[i] = 1LL * infact[i + 1] * (i + 1) % mod;
    power[0] = 1;
    for(int i = 1;i <= n;i ++)  power[i] = power[i - 1] * 10 % mod;
}

int C(int a,int b) {
	if(a<0||b<0||a<b) return 0;
	return fact[a] * infact[a - b] % MOD * infact[b] % MOD;
}
int f2[N], f1[N];
char str[N];
void solve(){
    int k, n;
    cin >> k >> (str + 1);
    int len = strlen(str + 1);
    f1[0] = 1;
    f2[0] = (k >= 2); // 特判k为1的情况
    for(int i = 1;i <= len;i ++) {
        f1[i] = (2LL * f1[i - 1] % mod - C(i - 1,k - 1) + 2 * mod) % mod;
        f2[i] = (2LL * f2[i - 1] % mod - C(i - 1,k - 2) + 2 * mod) % mod;
    } 
    int sum = 0;
    int ans = 0;
    for(int i = len;i > 0;i --) {
        int now = str[i] - '0';
        if(i < len) sum = (sum + 1LL * power[len - i - 1] * f2[i - 1] % mod) % mod;
        ans = (ans + 1LL * now * (sum + 1LL * power[len - i] * f1[i - 1] % mod) % mod + mod) % mod;
    }
    cout << ans << endl;
}

1006. GCD Game

  • 题意

给出长度为\(n\)的序列\(a\), 每次可以选择一个数字\(a_i\),然后选择一个数字\(x (1 <= x < a_i)\), 将\(a_i = gcd(a_i,x)\)

  • 思路

可以发现一个数字最多可以\(gcd\),它的质因子的次数之和次,然后就是一个经典的\(nim\)博弈,\(n\)堆石子,每次选择可以拿 \(1 \to f(a_i)\)个。

code :

int primes[M], tot;
int cnt[M];
bool st[M];

void init() {
    for(int i = 2;i < M;i ++) {
        if(!st[i]) {
            primes[++ tot] = i;
            cnt[i] = 1;
        }
        for(int j = 1; j <= tot && primes[j] * i < M;j ++) {
            cnt[i * primes[j]] = cnt[i] + 1;
            st[i * primes[j]] = 1;
            if(i % primes[j] == 0) break;
        }
    }
}


void solve(){
    int n;
    cin >> n;
    int ans = 0;
    for(int i = 1;i <= n;i ++) {
        int x;
        cin >> x;
        ans ^= cnt[x];
    }
    if(ans == 0) {
        cout << "Bob" << endl;
    }else {
        cout << "Alice" << endl;
    }
}

1008. Square Card

  • 题意

给两个圆,第一个圆是得分区域,第二个园是得奖区域,问现在随意向第一个圆内扔一个旋转的正方形,但这个正方形严格在圆内才可以算,问这个正方形即在得分区域又在得奖区域的概率。

  • 思路

画个图直接算,注意正方形不用转换成圆,用正方形取探圆的边缘区域,那么让正方形进来而需要减少的半径就是那一小段圆弧和 \(r / 2\)​​的路径。
st
就这橙线上面的剪掉, 两个圆都剪掉(然后正方形可以看成一个点了),然后两圆相交面积 / 大圆面积就是答案。

code :

#define PI acos(-1)
struct Point{
    double x,y;
    Point(){}
    Point(double _x,double _y) {
        x = _x;
        y = _y;
    }
    double distance(Point p){
        return hypot(x - p.x,y - p.y);
    }

};
// 求两圆相交的面积
double Area_of_overlap(Point c1, double r1, Point c2, double r2) 
{
    double d = c1.distance(c2);
    if (r1 + r2 < d + eps)
    {
        return 0;
    } 
    if (d < fabs(r1 - r2) + eps)
    {
        double r = min(r1, r2);
        return PI * r * r;
    }
    double x = (d * d + r1 * r1 - r2 * r2) / (2 * d);
    double t1 = acos(x / r1);
    double t2 = acos((d - x) / r2);
    return r1 * r1 * t1 + r2 * r2 * t2 - d * r1 * sin(t1);
}

void solve(){
    db r1,r2,x1,x2,y1,y2;
    sc("%lf%lf%lf", &r1, &x1, &y1);
    sc("%lf%lf%lf", &r2, &x2, &y2);
    db r;
    sc("%lf", &r);
    
    r = 1.0 * r / 2.0; // a / 2
    if(r > r1 || r > r2) {
        cout << "0.000000" << endl;
        return;
    }
    db cs = r / r1; // cos0
    db del1 = r1 - r1 * sin(acos(cs)); // 小圆弧内需要删的
    r1 -= del1 + r; // r1 - del1 - a / 2
    if(r1 < eps) {
        cout << "0.000000" << endl;
        return;
    }
    cs = r / r2; // cos0
    db del2 = r2 - r2 * sin(acos(cs)); // 小圆弧内需要删的
    r2 -= del2 + r; // r2 - del1 - a / 2
    if(r2 < eps) {
        cout << "0.000000" << endl;
        return;
    }
    Point a = Point(x1,y1), b = Point(x2,y2);
    if(a.distance(b) >= r1 + r2) {
        cout << "0.000000" << endl;
        return;
    }
    db inter = Area_of_overlap(a, r1, b, r2);
    db o = PI * r1 * r1;
    pr("%.6f\n", inter / o);
}

1009. Singing Superstar

  • 题意

给一个目标串和一堆模式串,让你去计算每个不重复的匹配子串个数(如 "\(aba\)" \(\in\) "\(ababa\)" = 1)

  • 思路

ac自动机模板改一下,或者哈希字符串匹配(记得开map,count()操作\(O(logn)\))

code :

const int  maxn = 100010;
struct node{ int index; char s[30];}a[maxn];
struct AC{
	int num,ch[6*maxn][30],f[6*maxn],last[6*maxn],val[6*maxn];
    int times[6*maxn],dep[7*maxn],pos[6*maxn];
	void init(){
		num=0;
        memset(ch,0,sizeof(ch));
        memset(f,0,sizeof(f));
        memset(last,0,sizeof(last));
        memset(val,0,sizeof(val));
        memset(times,0,sizeof(times));
        memset(dep,0,sizeof(dep));
        memset(pos,-1,sizeof(pos));
        return;
	}
	void insert(char *s,int qN){
        int u=0,len=strlen(s),id;
        for(int i=0;i<len;i++){
            id=s[i]-'a';
            if(!ch[u][id])ch[u][id]=++num;
            u=ch[u][id];
        }
        val[u]=1;
        dep[u]=len;
        a[qN].index=u;
        return;
    }
	void getfail(){
        queue<int> Q;
        Q.push(0);
        int x,u,v;
        while(!Q.empty()){
            x=Q.front();Q.pop();
            for(int i=0;i<26;i++){
                u=ch[x][i];
                if(!u){
                    ch[x][i]=ch[f[x]][i];
                    continue;
                }
                Q.push(u);
                if(x==0) continue;
                v=f[x];
                while(v&&!ch[v][i]) v=f[v];
                f[u]=ch[v][i];
                last[u]=val[f[u]]?f[u]:last[f[u]];
            }
        }
        return;
    }
	void find(char *s){
        memset(times,0,sizeof(times));
        int len=strlen(s),u=0,id,j;
        for(int i=0;i<len;i++){
            id=s[i]-'a';
            u=ch[u][id];
            j=u;
            do{
                if(i - pos[j] >= dep[j]){ // 不相等
                    times[j]++;
                    pos[j]=i;
                }
                j=last[j];
            }while(j);
        }
        return;
    }
}ac;
char to[N];
void solve(){
    int n;
    cin >> to;
    cin >> n;
    ac.init();
    for(int i = 1; i <= n; i++)
    {
        cin >> a[i].s;
        ac.insert(a[i].s, i);
    }
    ac.getfail();
    ac.find(to);
    for(int i = 1; i <= n; i++)
    {
        cout << ac.times[a[i].index] << endl;
    }
}
posted @ 2021-08-13 10:52  darker_wxl  阅读(183)  评论(0编辑  收藏  举报
window.onload = function(){ $("#live2dcanvas").attr("style","position: fixed; opacity: 0.7; left: 70px; bottom: 0px; z-index: 1; pointer-events: none;") }