2022NOIPA层联测33
A. GCD
<不稳定的道路>给人一种错觉就是遇到这种奇奇妙妙的图论题可以二话不说直接上最短路板子,然而用在这里就错了……这东西从始至终没用到最短路。
边长范围可以利用,用法根稠密图Kru的优化相似就是把边按长度分类,这个题保证没有重复长度连分类都没必要就是一个。
只改了暴力写法,枚举边长判连通性,但是跑起来却异常快!?导致直接不想写正解了!
code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 3e5 + 3;
const int mx = 1e5;
int n, m, q, ans[maxn], fa[maxn], v[maxn], vcnt, vis[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;
}
struct edge {int x, y;}e[maxn];
struct node
{
int x, id;
};
vector<node> que[maxn];
int find(int x) {return fa[x] == x ? x : fa[x] = find(fa[x]);}
int main()
{
freopen("gcd.in", "r", stdin);
freopen("gcd.out", "w", stdout);
n = read(), m = read(), q = read();
int to = -1;
for(int i=1; i<=m; i++)
{
int x = read(), y = read(), w = read();
if(x > y) swap(x, y);
e[w].x = x; e[w].y = y;
to = max(to, w);
}
for(int i=1; i<=q; i++)
{
int x = read(), y = read();
if(x > y) swap(x, y);
que[x].push_back((node){y, i});
}
fill(ans+1, ans+1+q, -1);
for(int i=1; i<=to; i++)
{
vcnt = 0;
for(int j=i; j<=to; j+=i)
{
if(!e[j].x) continue;
if(vis[e[j].x] != i) vis[e[j].x] = i, v[++vcnt] = e[j].x;
if(vis[e[j].y] != i) vis[e[j].y] = i, v[++vcnt] = e[j].y;
}
for(int j=1; j<=vcnt; j++) fa[v[j]] = v[j];
for(int j=i; j<=to; j+=i)
{
if(!e[j].x) continue;
fa[find(e[j].x)] = find(e[j].y);
}
for(int j=1; j<=vcnt; j++)
{
int ff = find(v[j]);
for(node x : que[v[j]])
{
if(vis[x.x] == i && find(x.x) == ff) ans[x.id] = i;
}
}
}
for(int i=1; i<=q; i++) printf("%d\n", ans[i]);
return 0;
}
B. 简单题
我居然背过了线性筛欧拉函数!考场上没记熟试了好多种写法终于淘到一种对的……想必以后就记住了。奇数的欧拉函数最大是n-1那就变成了偶数,偶数的欧拉函数最大是n/2,所以它变化的次数最多是一个log,le6的log约等于20.
然而并不需要像<誓约·胜利之剑>一样记录lim小于等于45、44之类的,设置区间是否完全覆盖就行了,那么就有点像<支配数据>,完全覆盖就树上查询,否则上ST表。
区间加1不代表所有的lazy标记都是1,我傻乎乎的这么想,把所有用lazy标记的部分全改成1还交了一版0分。
code
//誓约·胜利之剑!
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e6 + 2;
const int M = 2e5 + 3;
const int mod = 998244353;
int phi[maxn], n, m, prime[maxn], tot, a[M], v[M], mx;
bool not_prime[maxn];
typedef pair<int, int> pii;
inline void add(int &x, int y) {x=(x+y)%mod;}
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 get_phi(int n)
{
phi[1] = 1;
for(int i=2; i<=n; i++)
{
if(!not_prime[i])
{
prime[++tot] = i; phi[i] = i-1;
}
for(int j=1; j<=tot&&prime[j]*i<=n; j++)
{
not_prime[i*prime[j]] = 1;
phi[i*prime[j]] = phi[i] * ((i % prime[j]) ? (prime[j]-1) : prime[j]);
if(i % prime[j] == 0) break;
}
}
}
struct seg
{
struct node
{
int s1, s2, tag;
bool lim;
}t[maxn<<2];
void pushup(int x)
{
t[x].s1 = (t[x<<1].s1 + t[x<<1|1].s1) % mod;
t[x].s2 = (t[x<<1].s2 + t[x<<1|1].s2) % mod;
t[x].lim = (t[x<<1].lim && t[x<<1|1].lim);
}
void pushdown(int x, int l, int r)
{
int mid = (l + r) >> 1, ls = x<<1, rs = x<<1|1;
add(t[ls].s1, 1ll*(mid-l+1)*t[x].tag%mod);
add(t[rs].s1, 1ll*(r-mid)*t[x].tag%mod);
add(t[ls].tag, t[x].tag);
add(t[rs].tag, t[x].tag);
t[x].tag = 0;
//谁跟你说tag一定等于1了啊!!!
}
void build(int x, int l, int r)
{
if(l == r)
{
t[x].s1 = a[l]; t[x].s2 = v[l];
return;
}
int mid = (l + r) >> 1;
build(x<<1, l, mid);
build(x<<1|1, mid+1, r);
pushup(x);
}
void update(int x, int l, int r, int L, int R)
{
if(t[x].lim && L <= l && r <= R)
{
add(t[x].s1, r-l+1);
add(t[x].tag, 1);
return;
}
if(l == r)
{
add(t[x].s1, t[x].s2); t[x].s2 = phi[t[x].s2];
t[x].lim = (t[x].s2 == 1);
return;
}
if(t[x].tag) pushdown(x, l, r);
int mid = (l + r) >> 1;
if(L <= mid) update(x<<1, l, mid, L, R);
if(R > mid) update(x<<1|1, mid+1, r, L, R);
pushup(x);
}
pii query(int x, int l, int r, int L, int R)
{
if(L <= l && r <= R) return pii(t[x].s1, t[x].s2);
if(t[x].tag) pushdown(x, l, r);
int mid = (l + r) >> 1, ans = 0, res = 0;
if(L <= mid)
{
pii now = query(x<<1, l, mid, L, R);
add(ans, now.first), add(res, now.second);
}
if(R > mid)
{
pii now = query(x<<1|1, mid+1, r, L, R);
add(ans, now.first), add(res, now.second);
}
return pii(ans, res);
}
}t;
int main()
{
freopen("simple.in", "r", stdin);
freopen("simple.out", "w", stdout);
n = read(), m = read();
for(int i=1; i<=n; i++)
{
a[i] = read(), v[i] = read();
mx = max(mx, v[i]);
}
get_phi(mx);
t.build(1, 1, n);
while(m--)
{
int opt = read(), l = read(), r = read();
if(opt == 1)
{
t.update(1, 1, n, l, r);
}
else
{
pii ans = t.query(1, 1, n, l, r);
printf("%d %d\n", ans.first, ans.second);
}
}
return 0;
}
C. 建筑
先膜拜开<二维ST表>查区间最值完全n^4拿到40 pts的蓉儿。其实我是因为忘了二维数据结构怎么用才去想的怎么n^3……然而还是没有成功n^3……
模板_二维ST表
//蓉儿怎么这么巨啊!!!!
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 204;
int a[maxn][maxn], n, m;
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 ST_table
{
int mx[maxn][maxn][9][9], sum[maxn][maxn], lg2[maxn];
inline void init()
{
for(int i=1; i<=n; i++)
for(int j=1; j<=m; j++)
mx[i][j][0][0] = a[i][j];
for(int l1=0; l1<9; l1++)
for(int l2=0; l2<9; l2++)
for(int i=1; i+(1<<l1)-1<=n; i++)
for(int j=1; j+(1<<l2)-1<=m; j++)
{
if(!l1 && !l2) continue;
if(l1) mx[i][j][l1][l2] = max(mx[i][j][l1-1][l2], mx[i+(1<<l1-1)][j][l1-1][l2]);
else if(l2) mx[i][j][l1][l2] = max(mx[i][j][l1][l2-1], mx[i][j+(1<<l2-1)][l1][l2-1]);
}
for(int i=1; i<=n; i++)
for(int j=1; j<=m; j++) sum[i][j] = sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1]+a[i][j];
lg2[1] = 0;
for(int i=2; i<=max(n,m); i++) lg2[i] = lg2[i>>1] + 1;
}
inline int query_max(int a1, int b1, int a2, int b2)
{
int l1 = lg2[a2-a1+1], l2 = lg2[b2-b1+1];
int r1 = max(mx[a1][b1][l1][l2], mx[a2-(1<<l1)+1][b1][l1][l2]);
int r2 = max(mx[a1][b2-(1<<l2)+1][l1][l2], mx[a2-(1<<l1)+1][b2-(1<<l2)+1][l1][l2]);
return max(r1, r2);
}
inline ll query_sum(int a1, int b1, int a2, int b2)
{
return sum[a2][b2]-sum[a1-1][b2]-sum[a2][b1-1]+sum[a1-1][b1-1];
}
}st;
int main()
{
freopen("building.in", "r", stdin);
freopen("building.out", "w", stdout);
n = read(), m = read();
for(int i=1; i<=n; i++)
for(int j=1; j<=m; j++) a[i][j] = read();
st.init();
ll ans = 0;
for(int i=1; i<=n; i++)
for(int j=1; j<=m; j++)
for(int x=i; x<=n; x++)
for(int y=j; y<=m; y++)
{
ans += 1ll*st.query_max(i,j,x,y)*(y-j+1)*(x-i+1)-st.query_sum(i,j,x,y);
}
printf("%lld\n", ans);
return 0;
}
鹤了才发现我的50pts部分分(不开lpong long赛时变成了30)居然和正解很沾边!!
感觉所有序列上说什么用笛卡尔树的东西都可以用单调栈代替,比如《矩形》。
50% code
/*
二缺吧我是,调了俩小时才发现系数和最值一一对应只能单点查询!?
真是醉了还是n^4的。。。
*/
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 204;
int n, m, v[maxn][maxn], Mx[maxn][maxn][maxn];
int st[maxn], top;
ll ans, res1, res2, sum[maxn][maxn][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;
}
struct line
{
struct lines
{
ll mx, xs;
}t[maxn];
void clear(int n)
{
for(int i=1; i<=n; i++)
{
t[i].mx = t[i].xs = 0;
}
}
void update1(int l, int r, int v)
{
for(int i=l; i<=r; i++) t[i].mx = v;
}
void update2(int l, int r, int v)
{
for(int i=l; i<=r; i++) t[i].xs++;
}
int query(int l, int r)
{
int ans = 0;
for(int i=l; i<=r; i++) ans += t[i].mx*t[i].xs;
return ans;
}
}t;
int main()
{
freopen("building.in", "r", stdin);
freopen("building.out", "w", stdout);
n = read(), m = read();
for(int i=1; i<=n; i++)
{
for(int j=1; j<=m; j++) v[i][j] = read();
}
for(int i=1; i<=n; i++)
{
for(int l=1; l<=m; l++)
{
int su = 0, ma = 0;
for(int r=l; r<=m; r++)
{
su += v[i][r]; ma = max(ma, v[i][r]);
sum[i][l][r] = su, Mx[i][l][r] = ma;
}
}
}
for(int l=1; l<=m; l++)
{
for(int r=l; r<=m; r++)
{
top = 0; res1 = 0; res2 = 0;
t.clear(n);
for(int i=1; i<=n; i++)
{
while(top && Mx[st[top]][l][r] < Mx[i][l][r]) top--;
t.update1(st[top]+1, i, Mx[i][l][r]);
st[++top] = i;
t.update2(1, i, 1);
res1 += t.query(1, i);
}
res1 *= (r-l+1);
for(int i=1; i<=n; i++)
{
res2 += sum[i][l][r]*(n-i+1)*i;
}
ans += res1-res2;
}
}
printf("%lld\n", ans);
return 0;
}
差别在于:没有考虑根号,保持了矩阵的原有形态,比较T;把矩形压成序列不需要过度预处理,空间开不下;但是最主要的是我并不知道怎么O(n)算出序列的答案(应该是最重要的部分)。
本来想把最大值的系数鹤最大值自己拆开,放到线段树上变成互不干涉的两部分,然而它们的一一对应关系是不可能解除的,由于单点查询代表复杂度不可避免,我把它改成了暴力处理。
然而最终死于没开long long——连续的第二天!!不过1e5*1e4怎么看都不像是需要long long的样子?
有人能解释这题为什么要开long long吗……(我一开始只有ans和res1、res2时long long的,sum和假线段树里的都不是,后来发现sum和假线段树需要改long long)
前两天《串串超人》刚刚提醒过我们:只需要求和的东西不需要维护序列,这个题也是这样的。如果只是最大值求和就很显然,加上的区间长度也不难算,发现这些区间长度构成了等差序列,所以要用到等差序列求和公式。x是首项,y是末项,公差=1.
ll calc(ll x, ll y) {return (x+y)*(y-x+1)/2;}
这个东西鹤起来非常眼熟,因为在<矩形>里用过!
ll calc(ll s, ll w)
{
if(s < 0 || s-w+1 < 0) return 0;
return (s+s-w+1)*w/2;
}
当时Chen_jr大佬的原版用的是等差序列求和的形式,然而我并不知道求和公式只是感觉传的参数之间有交叉好麻烦于是就化简了一下,calc(s, w)表示长度为s的大区间内长度为w的子区间个数,这个东西我当时也是用等差序列求和来理解的,然而到了这个题上又忘了这个思路,所以这个题用到这个公式和定长区间统计并没有关系是我跑偏了……
ll calc(ll s, ll w){return s < 0 || w < 0 ? 0 : (s + w) * (s - w + 1) / 2;}
矩形那题%%%Chen_jr用到的式子本来长这样。
于是就结束了,add 存数据方式非常赞。
鹤得很明显
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e5 + 5;
int n, m, mx[maxn], st[maxn], top;
ll sval[maxn], ans;
vector<int> mp[maxn];
ll calc(ll x, ll y) {return (x+y)*(y-x+1)/2;}
void ckmx(int &x, int y) {if(x < y) x = y;}
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 sol(ll len)
{
ll now = 0, sum = 0, smax = 0; st[top = 0] = -1;
for(int i=0; i<m; i++)
{
sum += 1ll * sval[i] * (i + 1);
while(top && mx[st[top]] < mx[i])
{
smax -= 1ll * mx[st[top]] * (st[top] - st[top-1]);
now -= 1ll * mx[st[top]] * calc(i-1-st[top]+1, i-1-st[top-1]);
top--;
}
now += smax;
smax += 1ll * mx[i] * (i - st[top]);
now += 1ll * mx[i] * calc(1, i-st[top]);
st[++top] = i;
ans += 1ll * len * now - sum;
}
}
int main()
{
freopen("building.in", "r", stdin);
freopen("building.out", "w", stdout);
n = read(), m = read();
for(int i=1; i<=n; i++)
{
for(int j=1; j<=m; j++)
{
if(n < m) mp[i-1].push_back(read());
else mp[j-1].push_back(read());
}
}
if(n > m) swap(n, m);
for(int i=0; i<n; i++)
{
for(int j=0; j<m; j++) mx[j] = 0, sval[j] = 0;
for(int j=i; j<n; j++)
{
for(int k=0; k<m; k++) ckmx(mx[k], mp[j][k]), sval[k] += mp[j][k];
sol(j - i + 1);
}
}
printf("%lld\n", ans);
return 0;
}
D. 树上前缀和
终于有一次没把送分题做成送命题……