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)记录了初始值,然后定义两种操作。
- (forall i in [l,r]), 所有的星星减少(a_i & (-a_i))
- (forall i in [l,r]), (a_i eq),所有的星星增加(2^k),(2^k <= a_i <= 2^{k + 1})
- 思路
操作(1)就是二进制中最低位从(1 o 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 o 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}))即可。
注意(2sum_{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 o 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)的路径。
就这橙线上面的剪掉, 两个圆都剪掉(然后正方形可以看成一个点了),然后两圆相交面积 / 大圆面积就是答案。
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
", 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;
}
}