test20190818 NOIP2019 模拟赛

0+0+20=20,不给大样例,小数据又水,还没有题解的垃圾题。

A 题

问题描述:
long long ago, Lxhgww 统治的国家里有 n 个城市,其中某一个城市是 capital (首都),这 n 个城市构成以 capital 为根的有向树。

Lxhgww 会通过发送指令去派一些士兵去保卫这些城市。Lxhgww 发出的指令格式为 x,k,表示向 x 结点派送 k 个士兵,向 x 的子节点派送 k + 1 个士兵,向 x 的子节点的子节点派送 k + 2 个士兵,以此类推。

现在考古学家通过考古,发现了 Lxhgww 的国家的部分信息。考古学家只得到了这棵树的 n - 1 条边,但并不知道这 n - 1 条边的方向,也不知道哪个城市才是 capital。考古学家也得到了所有 Lxhgww 发出的指令。

考古学家们知道 Lxhgww 是一个非常聪明的人,所以他们认为:以 capital 为根,这些指令所派送的士兵总数一定是最少的。

现在考古学家想让你告诉他们,这些指令所派送的士兵总数的最小值是多少,以及哪些点有可能是 capital。

输入:
第一行读入两个整数 n,m,分别表示城市的个数以及指令的数量。
接下来 n - 1 行,每行读入两个数 ai, bi,表示 ai 与 bi 之间有一条边。(注意边是没有方向的)
接下来 m 行,每行读入两个数 x, k。

输出:
第一行输出一个整数,表示完成这些指令所需的最小值。 第二行输出若干个
数,表示可能是 capital 的节点,这些数按照从小到大的顺序输出。

样例输入:
5 2
1 5
1 3
1 2
2 4
1 1
3 1

样例输出:
6
2 4

数据范围:
对于 10%的数据,1 <= n, m <= 300
对于 40%的数据,1 <= n, m <= 5000
对于 100%的数据,1 <= n, m <= 500000, 0 <= k <= 1000, 1 <= ai, bi, x <= n

题解

将贡献拆分,就是\(k\times siz_x+\sum_{y \in subtree_x} dep_y-depx\)

注意到根从父亲变到自己的变动量只有父亲和自己,然后换根大力维护即可。时间复杂度\(O(n)\)

然后我没有注意到多次加到同一个点上时\(\sum dep\)要翻倍,也没有注意到\(k=0\)的情况。此题爆0。

#include<bits/stdc++.h>
using namespace std; 
template<class T> T read(){
	T x=0;char c=getchar();
	while(!isdigit(c)) c=getchar();
	for(;isdigit(c);c=getchar()) x=x*10+c-'0';
	return x;
}
template<class T> T read(T&x){
	return x=read<T>();
}
#define co const
#define il inline
#define int long long

co int N=500000+10;
int n,m,val[N],cnt[N]; // edit 1:cnt
vector<int> to[N];

int siz[N],dep[N],sd[N],sv[N];

void dfs1(int x,int fa){
	siz[x]=1,sd[x]=dep[x];
	for(int i=0;i<(int)to[x].size();++i){
		int y=to[x][i];
		if(y==fa) continue;
		dep[y]=dep[x]+1;
		dfs1(y,x);
		siz[x]+=siz[y],sd[x]+=sd[y],sv[x]+=sv[y];
	}
	if(cnt[x]) sv[x]+=val[x]*siz[x]+cnt[x]*(sd[x]-dep[x]*siz[x]); // edit 2:k=0
}

int ans=1e18;
vector<int> sol;

void dfs2(int x,int fa,int sumd,int sumv){
	if(fa){
		if(cnt[x]) sumv-=val[x]*siz[x]+cnt[x]*(sd[x]-dep[x]*siz[x]);
		if(cnt[fa]) sumv-=val[fa]*n+cnt[fa]*sumd;
		sumd-=sd[x]-dep[x]*siz[x]+siz[x];
		sumd+=n-siz[x]+sd[x]-dep[x]*siz[x];
		if(cnt[x]) sumv+=val[x]*n+cnt[x]*sumd;
		if(cnt[fa]) sumv+=val[fa]*(n-siz[x])+cnt[fa]*(sumd-(sd[x]-dep[x]*siz[x])-(n-siz[x]));
	}
	if(sumv<ans) ans=sumv,sol.assign(1,x);
	else if(sumv==ans) sol.push_back(x);
	for(int i=0;i<(int)to[x].size();++i){
		int y=to[x][i];
		if(y==fa) continue;
		dfs2(y,x,sumd,sumv);
	}
}

signed main(){
	freopen("A.in","r",stdin),freopen("A.out","w",stdout);
	read(n),read(m);
	for(int i=1;i<n;++i){
		int x=read<int>(),y=read<int>();
		to[x].push_back(y),to[y].push_back(x);
	}
	while(m--){
		int x=read<int>();
		val[x]+=read<int>(),++cnt[x];
	}
	dfs1(1,0);
	dfs2(1,0,sd[1],sv[1]);
	printf("%lld\n",ans);
	sort(sol.begin(),sol.end());
	for(int i=0;i<(int)sol.size();++i) printf("%lld ",sol[i]);
	return 0;
}

B 题

问题描述:
Falsy 是一个可爱的女孩,她十分喜欢数字,她的梦想就是成为一名数学老师。有一天当她在把玩她的数字的时候,她不小心碰到了一个变量 x。初始时 x 的值为 x0。她发现每触碰一次这个变量,x 的值就会变成(kx + b) mod P。现在 Falsy 想让这个变量的值变回初始的样子,请你告诉她最少需要触碰多少次这个变量,能使它的值从 x0 又变回 x0。

输入:
第一行包含一个整数 T,表示测试数据组数。
每次测试数据包含四个整数:k,b,x0,P。

输出:
对于每组测试数据输出一行包含一个整数,表示对应的答案,若无解则输出-1。

样例输入:
2
4 7 1 13
11 4 2 12

样例输出:
6
1

数据范围:
对于 20%的数据,1 <= P <= 106,1 <= T <= 10。
对于 100%的数据,0 <= k,b,x0 < P,1 <= P <= 109 + 9,1 <= T <= 100。

题解

大力推式子,发现变动\(n\)次的结果是:

\[k^n x+b\sum_{i=0}^{n-1}k^i=k^nx+b\frac{k^n-1}{k-1} \]

开始解方程

\[k^nx+b\frac{k^n-1}{k-1}=x \mod P\\ (k^n-1)(x+\frac{b}{k-1})=0 \mod P \]

我一开始直接通分化简得到

\[(k^n-1)[(k-1)x+b]=0 \mod P \]

谁知道这样做是错的。因为\(k-1\)不一定有逆元,所以从前一个推到这个没问题,但推回去就是错的。

王贝贝写出了正确的方程,必须要考虑进去\(P\)的变动。

\[(k^n-1)(x+\frac{b}{k-1})+mP=0\\ (k^n-1)[(k-1)x+b]+m(k-1)P=0 \]

以下用\([]\)代替\([(k-1)x+b]\)。这时如果将\(k^n-1\)\(m\)看成未知数去解方程

\[k^n-1=0 \mod \frac{(k-1)P}{\gcd([],(k-1)P)} \]

这样做的答案是正确的,但是时间复杂度\(O(T P)\),和暴力没有多大区别。

\(Q=\frac{P}{\gcd([],P)}\),标程通过解方程

\[(k^n-1)\frac{[]}{\gcd([],P)}=0 \mod Q\\ k^n=1 \mod Q \]

然后通过矩阵求逆???构造出了满足

\[(k^n-1)\frac{[]}{\gcd([],P)}+m(k-1)Q=0 \]

的答案。

\[\begin{bmatrix} b & x \end{bmatrix} \cdot \begin{bmatrix} 1 & 1\\ 0 & k \end{bmatrix} \]

std的代码

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <algorithm>
#include <queue>
#include <map>
#include <set>
#include <vector>
#include <string>
#include <stack>
#include <bitset>
#define INF 0x3f3f3f3f
#define eps 1e-8
#define FI first
#define SE second
using namespace std;
typedef long long LL;

inline int phi(int n) {
    int ret = n, a = n;
    for(int i = 2; i * i <= a; ++i) {
        if(a % i) continue;
        ret = ret / i * (i - 1);
        while(a % i == 0) a /= i;
    }
    if(a > 1) ret = ret / a * (a - 1);
    return ret;
}

int pow_mod(LL a, int p, int mod) {
    LL c = 1;
    while(p) {
        if(p & 1) c = c * a % mod;
        p >>= 1;
        a = a * a % mod;
    }
    return c;
}

void mul(int a[][2], int b[][2], int mod) {
    int c[2][2] = { {0, 0}, {0, 0} };
    for(int i = 0; i < 2; ++i) {
        for(int j = 0; j < 2; ++j) {
            for(int k = 0; k < 2; ++k) {
                c[i][j] = (c[i][j] + (LL)a[i][k] * b[k][j]) % mod;
            }
        }
    }
    memcpy(a, c, sizeof(c));
}

int k, b, x, p;
int cal(int n, int mod) {
    if(n == 0) return 1;
    int A[2][2] = { {1, 1}, {0, k} };
    int C[2][2] = { {1, 0}, {0, 1} };
    while(n) {
        if(n & 1) mul(C, A, mod);
        n >>= 1;
        mul(A, A, mod);
    }
    return (1LL * k * C[0][1] + 1) % mod;
}

int main() {
    freopen("B.in", "r", stdin);
    freopen("B.out", "w", stdout);
    int T;
    scanf("%d", &T);
    while(T--) {
        scanf("%d%d%d%d", &k, &b, &x, &p);
        if(k == 0) {
            puts(x == b ? "1" : "-1");
            continue;
        }
        if(k == 1) {
            if(b == 0) {
                puts("1");
                continue;
            }
            printf("%d\n", p / __gcd(p, b));
            continue;
        }
        int q = p / __gcd((LL)p, (LL)(k - 1) * x + b);
        if(q == 1) {
            puts("1");
            continue;
        }
        if(__gcd(k, q) != 1) {
            puts("-1");
            continue;
        }
        int f = phi(q), mi = f;
        for(int i = 1; i * i <= f; ++i) {
            if(f % i) continue;
            if(pow_mod(k, i, q) == 1) {
                mi = i; break;
            }
            if(pow_mod(k, f / i, q) == 1) mi = f / i;
        }
        int ans = cal(mi - 1, q);
        ans = q / __gcd(ans, q) * mi;
        printf("%d\n", ans);
    }
    fclose(stdin);
    fclose(stdout);
    return 0;
}

C 题

问题描述:
Fang Fang 是一个非常讨厌二进制数的人,尤其是 8 位的二进制数。某一天她遇到了 n 个 8 位二进制数,她决定要把它们全部消灭掉。Fang Fang 手上有 m 个武器。其中某些武器可以把所有 8 位二进制表示包含某个特定前缀的数全部消灭;某些武器可以把所有 8 位二进制表示包含某个特定后缀的数全部消灭。但是每使用一个武器都会消耗 Fang Fang 一定的 IQ值,现在你需要帮助她用最少的 IQ 值把所有数字消灭。

输入:
第一行读入两个整数 n,m,分别表示数字个数以及武器个数。
第二行包含 n 个整数 ai。
接下来 m 行,每行表示一个武器:
P s w:你可以消灭所有 8 位二进制表示中包含前缀 s 的数,消耗 w 点 IQ。
S s w:你可以消灭所有 8 位二进制表示中包含后缀 s 的数,消耗 w 点 IQ。

输出:
输出一行包含一个整数,表示对应的答案。若无法消灭所有的数,输出-1。

样例输入:
8 7
0 1 2 3 4 5 6 7
P 000001 1
P 0000000 1
S 10 1
S 11 1
S 00 1
S 01 1
P 0000001 3

样例输出:
4

数据范围:
对于 30%的数据,1 <= n <= 20,1 <= m <= 50;
对于 100%的数据,1 <= n <= 256,1 <= m <= 500,0 <= ai <= 255,1 <= w <= 1000,s 是一个 01 串,1 <= |s| <= 8。

题解

建出前缀和后缀的Trie树,考虑最小割模型。

把需要消灭的点用 INF 边连起来。

有 P 就在 P 对应前缀Trie树上把对应点父亲到自己的边的流量设为权值,没有 P 就把流量设成 INF。S 的做法同理。

然后跑最小割就行了。算是一道网络流好题。

posted on 2019-08-19 11:21  autoint  阅读(214)  评论(0编辑  收藏  举报

导航