T1:[图论/枚举]给出有边权无向图,边权保证互不相同,Q次询问从S到T的路径中,边权的gcd最大是多少。(n<=1e4,Q<=2e5,w<=1e6)
考场
根据之前的一道图论题经验,在最短路上加个“\(w=(reach_{time}+ci)/di\)”或者是取个gcd都可以玄学无脑跑最短路,于是上来打了个dij最长路(考虑到边权不同,所以一定是递减,那么经过这条边减少的价值就看成是负边权,所以没有正环,妥妥的Dij啊!)
正解
首先考场思路假了,因为gcd的取值和数本身的大小没有关系,换句话说,保留更新x的最大值不代表决策最优性,比如x=1e9+7>x=8,但是如果接下来的边是4,那么显然8更优。
考虑正解是怎么做的,贪心假了,只能枚举了。枚举gcd从大到小,这样询问第一次被update就是答案,就可以删除了。以x为gcd构成若干联通图,询问的s和t如果在同一联通图内x就是答案。
【1】快速确定联通块的边:边权开桶直接加【2】快速确定连通块和询问交集:bitset维护点的询问集合以及点的可达集合
卡常:【1】只操作本次边涉及到的集合【2】清空紧贴使用,不额外加复杂度
点击查看代码
// ubsan: undefined
// accoders
#include <bits/stdc++.h>
using namespace std;
#define rint register int
#define ll long long
#define ull unsigned long long
#define chu printf
inline ll re() {
ll x = 0, h = 1;
char ch = getchar();
while (ch < '0' || ch > '9') {
if (ch == '-')
h = -1;
ch = getchar();
}
while (ch <= '9' && ch >= '0') {
x = (x << 1) + (x << 3) + (ch ^ 48);
ch = getchar();
}
return x * h;
}
const int N = 3e4 + 10, Q = 3e5 + 10;
int maxn, n, m, q;
struct edge {
int u, v;
} e[(int)1e5 + 100];
struct que {
int u, v;
} qr[Q];
bitset<30001> s[N], g[N]; //对每个点维护询问集合?
int mx, fa[N];
vector<int> pt;
bool vis[N];
unordered_map<int, int> ans[Q];
inline void init() {
for (rint i = 1; i <= n; ++i) fa[i] = i, g[i][i] = 1;
}
inline int father(int x) { return (x == fa[x]) ? fa[x] : (fa[x] = father(fa[x])); }
inline void solve(int u, int x) //处理这个点
{
g[u] |= g[father(u)];
// for(rint i=1;i<=n;++i)if(g[u][i])chu("has the coed:%d %d\n",u,i);
g[u] &= s[u];
// chu("deal with:%d\n",u);
// for(rint i=1;i<=n;++i)if(s[u][i])chu("has queru:%d %d\n",u,i);
for (rint i = g[u]._Find_first(); i != g[u].size(); i = g[u]._Find_next(i)) {
// chu("it has :%d\n",i);
ans[u][i] = x;
s[u][i] = 0;
g[u][i] = 0;
}
g[u][u] = 1;
}
inline void unit(int u, int v) {
int fu = father(u), fv = father(v);
if (fu == fv)
return;
if (!vis[u])
pt.push_back(u), vis[u] = 1;
if (!vis[v])
pt.push_back(v), vis[v] = 1;
g[fu] |= g[fv];
fa[fv] = fu;
}
int main() {
// freopen("1.in","r",stdin);
// freopen("2.out","w",stdout);
freopen("gcd.in", "r", stdin);
freopen("gcd.out", "w", stdout);
n = re(), m = re(), q = re();
for (rint i = 1; i <= m; ++i) {
int u = re(), v = re(), w = re();
e[w] = (edge){ u, v };
mx = max(mx, w);
}
for (rint i = 1; i <= q; ++i) {
qr[i].u = re(), qr[i].v = re();
s[qr[i].u].set(qr[i].v);
}
init();
// printf("k:%d\n",mx);
for (rint i = mx; i >= 1; --i) {
// chu("for try:%d\n",i);
for (int j = i; j <= mx; j += i) {
if (e[j].u)
unit(e[j].u, e[j].v); // chu("add:%d %d\n",e[j].u,e[j].v);
}
// for(rint j=1;j<=n;++j)if(g[1][j])chu("has 1:%d %d\n",1,j);
for (rint j : pt)
if (fa[j] != j)
solve(j, i);
for (rint j : pt)
if (fa[j] == j)
solve(j, i);
for (rint to : pt) vis[to] = 0, fa[to] = to;
pt.clear();
}
for (rint i = 1; i <= q; ++i) {
if (ans[qr[i].u].count(qr[i].v) == 0)
chu("-1\n");
else
chu("%d\n", ans[qr[i].u][qr[i].v]);
}
return 0;
}
/*
5 5 3
1 2 48
2 4 24
3 2 32
1 3 18
3 4 96
1 3
1 4
3 5
【1】枚举gcd,枚举边,连图
【2】现在有若干连通块,枚举每个连通块处理询问:
枚举到块A的点k:
&一下,发现剩下了1,于是依次遍历每个位置上的1,处理询问,删除这个位置上的1
*/
T2[数据结构:线段树]给出序列A[x,y],有Q次询问:【1】opt=1,l,r:x+=y,y=phi[y]【2】opt=2,l,r:query_sum(x)and query_sum(y)。(n<=2e5)
考场
发现20次后phi=1,于是线段树维护了到目前的已经加了多少次(到20次就是1),维护2个值的区间和值,然后瞎搞了一堆线段树发现300行爆了,于是放弃
正解
只维护是否是y=1就行(否则到1了没到20次就浪费),而且分离询问和修改,修改特殊tag=1就直接+,询问按照正常写就行。
点击查看代码
// ubsan: undefined
// accoders
#include <bits/stdc++.h>
#define int ll
using namespace std;
#define rint register int
#define ll long long
#define ull unsigned long long
#define chu printf
inline ll re() {
ll x = 0, h = 1;
char ch = getchar();
while (ch < '0' || ch > '9') {
if (ch == '-')
h = -1;
ch = getchar();
}
while (ch <= '9' && ch >= '0') {
x = (x << 1) + (x << 3) + (ch ^ 48);
ch = getchar();
}
return x * h;
}
const int N = 1e6 + 10, M = 2e5 + 100;
const int mod = 998244353;
int zhi[N], prime[N], p[N];
int n, m;
pair<int, int> a[(int)2e5 + 100];
inline void upd(int& x, int y) {
x = x + y;
x = (x >= mod) ? (x - mod) : x;
}
inline void shai() {
p[1] = 1;
int cnt = 0;
for (rint i = 2; i <= 1000000; ++i) {
if (!prime[i])
zhi[++cnt] = i, p[i] = i - 1;
for (rint j = 1; 1ll * zhi[j] * i <= 1000000; ++j) {
int k = zhi[j] * i;
prime[k] = 1;
p[k] = p[i] * ((i % zhi[j]) ? (zhi[j] - 1) : zhi[j]);
if (i % zhi[j] == 0)
break;
}
}
}
struct segment {
int sum1[M << 2], sum2[M << 2], tag[M << 2];
bool cov[M << 2];
#define lson (rt << 1)
#define rson (rt << 1 | 1)
#define mid ((l + r) >> 1)
inline void pushup(int rt, int l, int r) {
sum1[rt] = sum1[lson] + sum1[rson];
sum2[rt] = sum2[lson] + sum2[rson];
sum1[rt] = (sum1[rt] >= mod) ? (sum1[rt] - mod) : sum1[rt];
sum2[rt] = (sum2[rt] >= mod) ? (sum2[rt] - mod) : sum2[rt];
cov[rt] = cov[lson] & cov[rson];
}
inline void pushdown(int rt, int l, int r) {
if (!tag[rt])
return;
upd(sum1[lson], tag[rt] * (mid - l + 1));
upd(sum1[rson], tag[rt] * (r - mid));
upd(tag[lson], tag[rt]);
upd(tag[rson], tag[rt]);
tag[rt] = 0;
}
inline void build(int rt, int l, int r) {
if (l == r) {
sum1[rt] = a[l].first;
sum2[rt] = a[l].second;
return;
}
build(lson, l, mid);
build(rson, mid + 1, r);
pushup(rt, l, r);
// chu("sum1[%d--%d]:%d\n",l,r,sum1[rt]);
}
inline void insert(int rt, int l, int r, int L, int R) {
if (l == r) {
upd(sum1[rt], sum2[rt]);
sum2[rt] = p[sum2[rt]];
cov[rt] = (sum2[rt] == 1);
// chu("sum1[%d--%d]:%d\n",l,r,sum1[rt]);
return;
}
pushdown(rt, l, r);
if (cov[rt] && L <= l && r <= R) //如果有标记,永远不需要下放,所以pushdown就很离谱
{
tag[rt]++;
upd(sum1[rt], r - l + 1);
return;
}
if (L <= mid)
insert(lson, l, mid, L, R);
if (R > mid)
insert(rson, mid + 1, r, L, R);
pushup(rt, l, r);
}
inline pair<int, int> query(int rt, int l, int r, int L, int R) //询问一对查
{
if (L <= l && r <= R) {
return make_pair(sum1[rt], sum2[rt]);
}
pushdown(rt, l, r);
if (R <= mid)
return query(lson, l, mid, L, R);
if (L > mid)
return query(rson, mid + 1, r, L, R);
pair<int, int> r1, r2;
r1 = query(lson, l, mid, L, R);
r2 = query(rson, mid + 1, r, L, R);
upd(r1.first, r2.first);
upd(r1.second, r2.second);
return r1;
}
} seg;
signed main() {
// freopen("1.in","r",stdin);
// freopen("1.out","w",stdout);
freopen("simple.in", "r", stdin);
freopen("simple.out", "w", stdout);
shai();
n = re(), m = re();
int mx = 0;
for (rint i = 1; i <= n; ++i) {
a[i].first = re(), a[i].second = re();
}
seg.build(1, 1, n);
for (rint i = 1; i <= m; ++i) {
int opt = re(), l = re(), r = re();
if (opt == 1) {
// chu("update:%lld %lld\n",l,r);
seg.insert(1, 1, n, l, r);
// for(rint j=1;j<=n;++j)
// {
// chu("for(%lld):%lld %lld\n",j,seg.query(1,1,n,j,j).first,seg.query(1,1,n,j,j).second);
// }
} else {
pair<int, int> o = seg.query(1, 1, n, l, r);
chu("%lld %lld\n", o.first, o.second);
}
}
return 0;
}
/*
5 5
19 20
7 17
9 16
9 1
16 1
1 1 5
1 1 3
1 1 5
1 5 5
2 1 5
4 5
0 3
0 10
0 8
1 4
1 1 4
2 1 4
1 1 4
1 1 4
2 1 2
线段树
【1】mi,tag1:
支持区间+1,区间求最小值
【2】cnt1,sum1
支持单点修改,区间求和
【3】cnt2,sum2
单点修改,区间求和
s1[i][j]:x的i位置操作j次后变成的值是多少
s2[i][j]:y的
对于比暴力还劣质的线段树唯一的优化:
当mi>=20不再继续向下递归
*/
// inline int phi(int x)
// {
// int ans=x;int bef=x;
// for(rint i=2;i<=bef;++i)
// {
// if(x%i==0)
// {
// int u=i;
// ans=ans*(u-1)/u;
// while(x>1&&x%u==0)x/=u;
// }
// if(x==1)break;
// }
// if(x!=1)ans=ans*(x-1)/x;
// return ans;
// }
T3[计数/单调栈]给出n行m列的矩形,求子矩形变成区间最大值的代价和。(n*m<=1e5)
考场
瞬间暴力想到ST表,发现200*200的部分分完全给得下,然后突然发现枚举基数\(n^4\),寄了...其实题库里是有50分的,TLEcodersT了1个点。想到要拍扁矩形统计以点为mx的贡献,发现不会。
正解
有2个切入点,枚举min(n,m)的话,n ^ 2*m是可以接受的【1】那就枚举少的n^ 2,这样确定了一个范围,立体的mx和sum就变成一个点了,直接预处理。【2】考虑区间mx的贡献,肯定是单调栈,首先肯定是无脑拍扁。
然后假设框定了l,r,sum和mx维护“点”的点权值和点和值,\(dev=-子序列的权值和+子序列max*区间长度*(r-l+1)\)
第一部分考虑每个点在子区间出现的次数:\(i*(m-i+1)\)
第二部分先用单调栈统计数字作用区间长度L,R:长度考虑左边长度和*包含右边区间个数+右边同+中间的数单独考虑,就是\(L*(L+1)/2*(R+1)+R*(R+1)/2*(L+1)+(R+1)*(L+1)\)
一些细节:空间开不下可以map,sum和mx直接随区间移动动态维护,不用预处理(否则会RE)
关于以上这些东西我想了半天本来以为是纯暴力想水50分,结果它竟然是正解?笛卡尔树呢?
点击查看代码
// ubsan: undefined
// accoders
#include <bits/stdc++.h>
using namespace std;
#define rint register int
#define ll long long
#define ull unsigned long long
#define chu printf
inline ll re() {
ll x = 0, h = 1;
char ch = getchar();
while (ch < '0' || ch > '9') {
if (ch == '-')
h = -1;
ch = getchar();
}
while (ch <= '9' && ch >= '0') {
x = (x << 1) + (x << 3) + (ch ^ 48);
ch = getchar();
}
return x * h;
}
const int N = 1e5+100;
unordered_map<int,int>a[400];
ll sum[(int)1e5+10];
int mx[(int)1e5+10];
int n,m,stk[N],top,L[N],R[N];
inline ll calc(int l,int r)
{
ll res= 1ll*l*(l+1)/2*(r+1)+1ll*r*(r+1)/2*(l+1)+1ll*(r+1)*(l+1);
// chu("%d %d is :%lld\n",l,r,res);
return res;
}
int main()
{
// freopen("1.in","r",stdin);
// freopen("2.out","w",stdout);
freopen("building.in", "r", stdin);
freopen("building.out", "w", stdout);
n=re(),m=re();
bool fan=0;
if(n>m)swap(n,m),fan=1;
if(fan)
{
for(rint i=1;i<=m;++i)
{
for(rint j=1;j<=n;++j)
{
a[j][i]=re();
}
}
}
else
{
for(rint i=1;i<=n;++i)
for(rint j=1;j<=m;++j)a[i][j]=re();
}
ll ans=0;
for(rint l=1;l<=n;++l)
{
for(rint i=1;i<=m;++i)mx[i]=0,sum[i]=0;
for(rint r=l;r<=n;++r)
{
for(rint i=1;i<=m;++i)mx[i]=max(mx[i],a[r][i]),sum[i]+=a[r][i];
ll dev=0;
top=0;
for(rint i=1;i<=m;++i)L[i]=1,R[i]=m;
for(rint i=1;i<=m;++i)//ele[l][r][what]
{
while(top&&mx[stk[top]]<=mx[i])
{
R[stk[top--]]=i-1;
}
L[i]=stk[top]+1;
stk[++top]=i;
}
for(rint i=1;i<=m;++i)
dev-=sum[i]*(i)*(m-i+1);//chu("%lld * %d* %d\n",sum[l][r][i],i,m-i+1);
stk[top+1]=m+1;
// chu("last dev:%lld\n",dev);
for(rint i=1;i<=m;++i)
{
// chu()
// chu("L[%d]:%d R:%d\n",i,L[i],R[i]);
dev+=calc(i-L[i],R[i]-i)*(r-l+1)*mx[i];
}
ans+=dev;
//chu("for:%d--%d dev is :%lld\n",l,r,dev);
}
}
chu("%lld",ans);
return 0;
}
/*
预处理mx[l][r][c]:横着是[l,r]竖着只考虑c列的mx
sum需要统一求
2 2
1 2 3 4
4 3
233 666 555
333 333 444
444 555 444
233 332 233
3 4
2 3 1 4
5 8 1 3
2 4 3 8
,chu("for pos :%d is :%lld(%d %d %d) * %d * %d\n",i,sum[l][r][i],l,r,i,(i),(m-i+1));
chu("only for the subsequence:%lld\n",-dev);
*/