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-1\)不一定有逆元,所以从前一个推到这个没问题,但推回去就是错的。
王贝贝写出了正确的方程,必须要考虑进去\(P\)的变动。
以下用\([]\)代替\([(k-1)x+b]\)。这时如果将\(k^n-1\)和\(m\)看成未知数去解方程
这样做的答案是正确的,但是时间复杂度\(O(T P)\),和暴力没有多大区别。
令\(Q=\frac{P}{\gcd([],P)}\),标程通过解方程
然后通过矩阵求逆???构造出了满足
的答案。
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 的做法同理。
然后跑最小割就行了。算是一道网络流好题。