2022NOIPA层联测30
A. 分配
爆 long long 的处理方法是分解质因数,然而当时由于认为这个数的上限是 2e52e5 (每条边都是1 / n)不仅不知道要筛多少质数,还不知道这个答案应该用多大的数组存储,后来发现分子分母都小于n,所以不管这个数有多大,质因子肯定都在n以内啊!所以质因数于是筛到n就够了!
经过每条边是分母是缺失的,分子是提供的,在路径上的任意位置只要不够消掉分母就要*所以取max,dfs回溯的方式除了把变量作为参数传进函数里还可以直接回溯。
根是谁都行,考场上这么写是为了让它少爆几次 long long ,结果并没有。
code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 2e5 + 2;
const int mod = 998244353;
int T, n, du[maxn], val[maxn], root;
ll ans;
inline int read()
{
int x = 0, f = 1;
char ch = getchar();
while(ch < '0' || ch > '9')
{
if(ch == '-')
{
f = -1;
}
ch = getchar();
}
while(ch >= '0' && ch <= '9')
{
x = (x << 1) + (x << 3) + (ch^48);
ch = getchar();
}
return x * f;
}
struct node
{
int nxt, to, fz, fm;
}a[maxn<<1];
int head[maxn], len;
inline ll gcd(ll a, ll b)
{
while(b^=a^=b^=a%=b);
return a;
}
void add(int x, int y, int fz, int fm)
{
a[++len].to = y; a[len].nxt = head[x];
int d = gcd(fz, fm); fz /= d; fm /= d; a[len].fz = fz; a[len].fm = fm;
head[x] = len;
}
/*--------------------------------------------------------------*/
int pre[maxn+16], vis[maxn+16], p[maxn+10], tot;
int res[maxn], v[maxn];
void primes()
{
pre[1] = 1;
for(int i=2; i<=n; i++)
{
if(!vis[i]) p[++tot] = i, pre[i] = 1;
for(int j=1; i*p[j]<=n&&j<=tot; j++)
{
vis[i*p[j]] = 1; pre[i*p[j]] = i;
if(i % p[j] == 0) break;
}
}
}
void mul(int x)
{
for(; x!=1; x=pre[x])
{
res[x/pre[x]] = max(res[x/pre[x]], ++v[x/pre[x]]);
}
}
void del(int x)
{
for(; x!=1; x=pre[x])
{
v[x/pre[x]]--;
}
}
/*--------------------------------------------------------------------*/
void dfs(int x, int fa)
{
for(int i=head[x]; i; i=a[i].nxt)
{
int v = a[i].to;
if(v == fa) continue;
mul(a[i].fm); del(a[i].fz);
dfs(v, x);
del(a[i].fm); mul(a[i].fz);
}
}
inline ll qpow(ll a, ll b)
{
ll ans = 1;
while(b)
{
if(b & 1) ans = ans * a % mod;
a = a * a % mod;
b >>= 1;
}
return ans;
}
void dfs2(int x, int fa)
{
for(int i=head[x]; i; i=a[i].nxt)
{
int v = a[i].to;
if(v == fa) continue;
val[v] = 1ll*val[x]*a[i].fz%mod*qpow(a[i].fm, mod-2)%mod;
dfs2(v, x);
}
}
void solve()
{
for(int i=1; i<n; i++)
{
int x = read(), y = read(), a = read(), b = read();
add(x, y, b, a); add(y, x, a, b);
if(a < b) du[y]++;
else du[x]++;
}
for(int i=1; i<=n; i++) if(du[i] == 0) {root = i; break;}
val[root] = 1;
dfs(root, 0);
for(int i=1; i<=n; i++) val[root] = 1ll*val[root]*qpow(i,res[i])%mod;
dfs2(root, 0);
for(int i=1; i<=n; i++)
{
ans = (ans + val[i]) % mod;
}
printf("%lld\n", ans);
}
void init()
{
n = read();
fill(head+1, head+1+n, 0);
fill(du+1, du+1+n, 0);
len = 0; root = 0; ans = 0;
fill(v+1, v+1+n, 0);
fill(res+1, res+1+n, 0);
}
int main()
{
freopen("arrange.in", "r", stdin);
freopen("arrange.out", "w", stdout);
n = maxn+2; primes();//筛多少啊
T = read();
while(T--)
{
init();
solve();
}
return 0;
}
B. 串串超人
如果我没记错的话(应该没有),对于求mex应该是把左端点是1右端点是1~n放到控制区间1~n的线段树上,然后掉进去出不来了,删除一个位置的贡献不如插入更好算,所以这道题移动的是右端点。
并且求和跟求最大值有一个非常明显的区别那就是要记录贡献的话连线段树都不用建,区间加法直接加就得了。
题解有一个非常形象的图,但是他说左端点是最靠右的且后缀长度为 i - l + 1 的位置(后缀长度不大,最后一次出现直接用数组记),但是这个后缀和当前的前缀相等时显然也没有贡献,于是我直接把它改成了后缀长度为 i - l 的位置,结果错的很离谱,又发现上一段第一个1(要找的这些位置都是上一段的)可以拓展到往左走最后一个0,数了半天0然后还是错的。
发现我要找的位置就是 i - l + 1 的下一个!绕了半天又绕回来了……
虽然是两层循环,但原理和单调栈一样,所以应该是线性的。
code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 5e5 + 2;
const int mod = 998244353;
char s[maxn];
int n, pre[maxn], suf[maxn], fir;
ll ans, sum;
inline int read()
{
int x = 0, f = 1;
char ch = getchar();
while(ch < '0' || ch > '9')
{
if(ch == '-')
{
f = -1;
}
ch = getchar();
}
while(ch >= '0' && ch <= '9')
{
x = (x << 1) + (x << 3) + (ch^48);
ch = getchar();
}
return x * f;
}
int main()
{
freopen("string.in", "r", stdin);
freopen("string.out", "w", stdout);
n = read();
scanf("%s", s+1);
int len = 0;
for(int i=n; i>=1; i--)
{
if(s[i] == '1') len++;
else len = 0;
suf[i] = len;
}
for(int i=1; i<=n; i++)
{
if((i == 1 && s[i] == '1') || (s[i] == '1' && s[i-1] == '0'))
{
if(fir)
{
for(int j=fir; j<i; j++)
{
pre[suf[j]] = j;
if(s[j] == '0') break;
}
}
fir = i;
}
if(s[i] == '1') sum += i - pre[i-fir+1];
ans += sum;
}
printf("%lld\n", ans);
return 0;
}
C. 多米诺游戏
本来以为可以得到 x = y 的部分分,但是我输出方案的时候输出了空格!?特判+输出“-1” = 20 pts,正解暂且咕咕咕了。
D. 大师
奇妙的题意转化:把奇数位异或1,操作就变成了交换相邻的两个不同数。
I think x 被经过的次数指的是以 x 为左边的点进行操作的次数,合法的情况最后一个点一定不会被操作因为∑a = ∑b。每个点被经过的次数都是abs(a的前缀和-b的前缀和)。
code
//鹤了
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 2005;
const int base = 2003;
const int mod = 1e9 + 7;
int T, n, a[maxn], b[maxn], f[maxn][maxn+maxn], g[maxn][maxn+maxn];
char s[maxn], t[maxn];
inline int read()
{
int x = 0, f = 1;
char ch = getchar();
while(ch < '0' || ch > '9')
{
if(ch == '-')
{
f = -1;
}
ch = getchar();
}
while(ch >= '0' && ch <= '9')
{
x = (x << 1) + (x << 3) + (ch^48);
ch = getchar();
}
return x * f;
}
void add(int &x, int y) {x += y; if(x >= mod) x -= mod;}
void solve()
{
n = read();
scanf("%s%s", s+1, t+1);
for(int i=1; i<=n; i++) a[i] = s[i]=='?'?-1:s[i]-'0';
for(int i=1; i<=n; i++) b[i] = t[i]=='?'?-1:t[i]-'0';
for(int i=1; i<=n; i+=2)
{
if(a[i] != -1) a[i] ^= 1;
if(b[i] != -1) b[i] ^= 1;
}
f[0][base] = 1;
for(int i=0; i<n; i++)
{
for(int j=-i; j<=i; j++) if(f[i][j+base])
{
int la = a[i+1] == -1 ? 0 : a[i+1], ra = a[i+1] == -1 ? 1 : a[i+1];
int lb = b[i+1] == -1 ? 0 : b[i+1], rb = b[i+1] == -1 ? 1 : b[i+1];
for(int p=la; p<=ra; p++)
{
for(int q=lb; q<=rb; q++)
{
add(g[i+1][j+base+p-q], (g[i][j+base]+1ll*abs(j+p-q)*f[i][j+base])%mod);
add(f[i+1][j+base+p-q], f[i][j+base]);
}
}
}
}
printf("%d\n", g[n][base]);
for(int i=0; i<=n; i++)
for(int j=-i-3; j<=i+3; j++) f[i][j+base] = g[i][j+base] = 0;
}
int main()
{
freopen("master.in", "r", stdin);
freopen("master.out", "w", stdout);
T = read();
while(T--) solve();
return 0;
}