2021.9.8 模拟赛
前言
手残+脑残=我。
T1 [SHOI2016]随机序列 加强版
题目
加强版数据有 \(a_i=0\) 的情况,考试没过是因为有个地方的 \(-1\) 打掉了,然而可以过没 \(0\) 的情况,所以样例没出锅。
加强版
//12252024832524
#include <set>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define TT template<typename T>
using namespace std;
typedef long long LL;
const int MAXN = 100005;
const int MOD = 1e9 + 7;
int n,m;
int a[MAXN];
LL Read()
{
LL x = 0,f = 1;char c = getchar();
while(c > '9' || c < '0'){if(c == '-')f = -1;c = getchar();}
while(c >= '0' && c <= '9'){x = (x*10) + (c^48);c = getchar();}
return x * f;
}
TT void Put1(T x)
{
if(x > 9) Put1(x/10);
putchar(x%10^48);
}
TT void Put(T x,char c = -1)
{
if(x < 0) putchar('-'),x = -x;
Put1(x); if(c >= 0) putchar(c);
}
TT T Max(T x,T y){return x > y ? x : y;}
TT T Min(T x,T y){return x < y ? x : y;}
TT T Abs(T x){return x < 0 ? -x : x;}
int qpow(int x,int y)
{
int ret = 1;
while(y){if(y & 1) ret = 1ll * ret * x % MOD;x = 1ll * x * x % MOD;y >>= 1;}
return ret;
}
#define lc (x<<1)
#define rc (x<<1|1)
int s[MAXN << 2],val[MAXN << 2],lz[MAXN << 2],PRE = 1;
int Ad(int x){if(x >= MOD) x -= MOD; if(x < 0) x += MOD; return x;}
void up(int x){s[x] = Ad(s[lc] + s[rc]);}
void calc(int x,int val){s[x] = 1ll * s[x] * val % MOD;lz[x] = 1ll * lz[x] * val % MOD;}
void down(int x)
{
if(lz[x] == 1) return;
calc(lc,lz[x]);
calc(rc,lz[x]);
lz[x] = 1;
}
void Build(int x,int l,int r)
{
lz[x] = 1;
if(l == r)
{
PRE = 1ll * PRE * a[l] % MOD;
if(l < n) s[x] = 2ll * PRE * qpow(3,n-l-1) % MOD;
else s[x] = PRE;
return;
}
int mid = (l+r) >> 1;
Build(lc,l,mid); Build(rc,mid+1,r);
up(x);
}
void Mul(int x,int l,int r,int ql,int qr,int v)
{
if(ql <= l && r <= qr)
{
calc(x,v);
return;
}
down(x);
int mid = (l+r) >> 1;
if(ql <= mid) Mul(lc,l,mid,ql,qr,v);
if(mid+1 <= qr) Mul(rc,mid+1,r,ql,qr,v);
up(x);
}
int Query(int x,int l,int r,int ql,int qr)
{
if(ql > qr) return 0;
if(ql <= l && r <= qr) return s[x];
down(x);
int mid = (l+r) >> 1,ret = 0;
if(ql <= mid) ret += Query(lc,l,mid,ql,qr);
if(mid+1 <= qr) ret += Query(rc,mid+1,r,ql,qr);
return Ad(ret);
}
set<int> cao;
set<int>::iterator it;
int main()
{
// freopen("task.in","r",stdin);
// freopen("task.out","w",stdout);
n = Read(); m = Read();
for(int i = 1;i <= n;++ i)
{
a[i] = Read();
if(!a[i]) a[i] = 1,cao.insert(i);
}
Build(1,1,n);
cao.insert(n+1);
for(int i = 1;i <= m;++ i)
{
int pos = Read(),v = Read();
if(!v) cao.insert(pos);
else
{
it = cao.lower_bound(pos);
if((*it) == pos) cao.erase(it);
Mul(1,1,n,pos,n,1ll * qpow(a[pos],MOD-2) * v % MOD);
a[pos] = v;
}
it = cao.begin();
Put(Query(1,1,n,1,*it-1),'\n');//曾经,这里没有-1
}
return 0;
}
题解
挖掘性质后发现能加就能减,对答案没有贡献,所以只考虑前缀中没有加减的情况,即只有乘法。
直接线段树维护一下即可,有 \(0\) 就相当于在中间夹断,求答案的时候不全局求。
T2 牛客第五场A题 Portal
题目
由于我看的可能是魔改过后的题目,题意可能会有区别,但是本质是一样的。
题解
首先我们把取货点和送货点都看成必经打卡点。
由这道题的启发,可知我们DP不需要记录当前人在哪里,因为一定在某个打卡点处。
然后挖掘性质,发现如果我们要使用传送门,原地开一个当起点就好,所以只用记录一个传送门的位置。
然后就可以 \(O(n^2k)\) DP了,数组可以滚动,但没必要,但如果不滚动记得两倍空间。
没滚动的代码
//12252024832524
#include <cstdio>
#include <cstring>
#include <algorithm>
#define TT template<typename T>
using namespace std;
typedef long long LL;
const int MAXN = 305;
const LL INF = 1ll << 60;
int n,m,s;
LL Read()
{
LL x = 0,f = 1;char c = getchar();
while(c > '9' || c < '0'){if(c == '-')f = -1;c = getchar();}
while(c >= '0' && c <= '9'){x = (x*10) + (c^48);c = getchar();}
return x * f;
}
TT void Put1(T x)
{
if(x > 9) Put1(x/10);
putchar(x%10^48);
}
TT void Put(T x,char c = -1)
{
if(x < 0) putchar('-'),x = -x;
Put1(x); if(c >= 0) putchar(c);
}
TT T Max(T x,T y){return x > y ? x : y;}
TT T Min(T x,T y){return x < y ? x : y;}
TT T Abs(T x){return x < 0 ? -x : x;}
LL dis[MAXN][MAXN],dp[MAXN << 1][MAXN];//任务i,传送门在j
int pos[MAXN << 1];
int main()
{
// freopen("express.in","r",stdin);
// freopen("express.out","w",stdout);
n = Read(); m = Read(); s = Read() << 1;
for(int i = 1;i <= n;++ i)
for(int j = 1;j <= n;++ j)
if(i^j) dis[i][j] = INF;
for(int i = 1,u,v;i <= m;++ i)
u = Read(),v = Read(),dis[u][v] = dis[v][u] = Min(dis[u][v],Read());
for(int k = 1;k <= n;++ k)
for(int i = 1;i <= n;++ i)
for(int j = 1;j <= n;++ j)
dis[i][j] = Min(dis[i][j],dis[i][k]+dis[k][j]);
for(int i = 1;i <= s;++ i) pos[i] = Read();
memset(dp,0x3f,sizeof(dp));
for(int i = 1;i <= n;++ i) //枚举传送门
for(int j = 1;j <= n;++ j)//中转传送门
dp[1][i] = Min(dp[1][i],dis[1][j]+dis[i][j]+Min(dis[i][pos[1]],dis[j][pos[1]]));
for(int i = 2;i <= s;++ i)
for(int j = 1;j <= n;++ j)
{
dp[i][j] = Min(dp[i][j],dp[i-1][j]+dis[pos[i-1]][pos[i]]);
for(int k = 1;k <= n;++ k)
dp[i][k] = Min(dp[i][k],dp[i-1][j]+dis[j][k]+Min(dis[k][pos[i]],dis[j][pos[i]])),//通过传送门去放传送门
dp[i][k] = Min(dp[i][k],dp[i-1][j]+dis[pos[i-1]][k]+Min(dis[k][pos[i]],dis[j][pos[i]]));//走去放传送门,再通过传送门到当前点
}
LL ans = INF;
for(int i = 1;i <= n;++ i) ans = Min(ans,dp[s][i]);
Put(ans);
return 0;
}
T3 牛客第五场B题 Graph
题目
此条目同上。
题解
挖掘性质后发现其实就是最小异或生成树,然后我不会,只能糊一个 \(\tt Prim\) 上去拿了个 \(O(n^2)\) 的 \(70pts\)。
正解是改进的 \(\tt Boruvka\),每轮操作可以不用 \(O(m)\) 枚举,而是 \(O(n\log_2a_i)\) 在字典树或者权值线段树上查询。
由于要动态维护连通性,还要询问异或最小,所以用并查集+线段树合并,用主席树查询的思想做就行了。
还有一个常数小和代码短的做法是权值分治,考虑分 \(\log_2a_i\) 层,每层按当前层分为 \(01\) 两部分,显然层级越高我们希望连的边越少,所以让左右子树分别连通之后左右 \(01\) 两部分只有连 \(1\) 条边即可,至于连的边嘛,当然是一半插入 Trie ,另一半查询咯。
两个做法时间复杂度是一样的,都是 \(O(n\log_2n\log_2a_i)\)。
线段树合并大常数做法
//12252024832524
#include <cstdio>
#include <cstring>
#include <algorithm>
#define TT template<typename T>
using namespace std;
typedef long long LL;
const int MAXN = 200005;
const int INF = 1 << 30;
int n;
LL Read()
{
LL x = 0,f = 1;char c = getchar();
while(c > '9' || c < '0'){if(c == '-')f = -1;c = getchar();}
while(c >= '0' && c <= '9'){x = (x*10) + (c^48);c = getchar();}
return x * f;
}
TT void Put1(T x)
{
if(x > 9) Put1(x/10);
putchar(x%10^48);
}
TT void Put(T x,char c = -1)
{
if(x < 0) putchar('-'),x = -x;
Put1(x); if(c >= 0) putchar(c);
}
TT T Max(T x,T y){return x > y ? x : y;}
TT T Min(T x,T y){return x < y ? x : y;}
TT T Abs(T x){return x < 0 ? -x : x;}
int head[MAXN],etot;
struct edge
{
int v,w,nxt;
}e[MAXN << 1];
void Add_Edge(int x,int y,int z)
{
e[++etot].v = y;
e[etot].w = z;
e[etot].nxt = head[x];
head[x] = etot;
}
void Add_Double_Edge(int x,int y,int z)
{
Add_Edge(x,y,z);
Add_Edge(y,x,z);
}
int d[MAXN],val[MAXN];
void dfs(int x)
{
for(int i = head[x]; i ;i = e[i].nxt)
if(!d[e[i].v])
d[e[i].v] = d[x] + 1,val[e[i].v] = val[x] ^ e[i].w,dfs(e[i].v);
}
bool vis[MAXN];
int od[MAXN],f[MAXN];
bool cmp(int x,int y){return val[x] < val[y];}
int findSet(int x)
{
if(f[x]^x) f[x] = findSet(f[x]);
return f[x];
}
int rt[MAXN],tot,who;
int ch[MAXN*60][2],siz[MAXN*60],ID[MAXN*60];
void Add(int &x,int v,int rk)
{
if(!x) x = ++tot;
++siz[x];
if(rk < 0) {ID[x] = who;return;}
bool to = v >> rk & 1;
Add(ch[x][to],v,rk-1);
}
int mge(int x,int y)
{
if(!x || !y) return x|y;
siz[x] += siz[y];
ch[x][0] = mge(ch[x][0],ch[y][0]);
ch[x][1] = mge(ch[x][1],ch[y][1]);
return x;
}
void unionSet(int u,int v)
{
u = findSet(u); v = findSet(v);
if(u^v) f[v] = u,rt[u] = mge(rt[u],rt[v]);
}
int MIN[MAXN],CID[MAXN],ori;
void Query(int lst,int x,int v,int rk)
{
if(v >= MIN[who]) return;
if(rk < 0)
{
CID[who] = ID[lst];
MIN[who] = v;
return;
}
bool to = val[ori] >> rk & 1;
if(siz[ch[lst][to]] - siz[ch[x][to]]) Query(ch[lst][to],ch[x][to],v,rk-1);
else Query(ch[lst][to^1],ch[x][to^1],v|(1<<rk),rk-1);
}
int main()
{
// freopen("xor.in","r",stdin);
// freopen("xor.out","w",stdout);
n = Read();
for(int i = 1,u,v;i < n;++ i)
{
u = Read()+1,v = Read()+1;
Add_Double_Edge(u,v,Read());
}
d[1] = 1,dfs(1);
for(int i = 1;i <= n;++ i) val[i] = Read();
for(int i = 1;i <= n;++ i) od[i] = f[i] = i,MIN[i] = INF;
int cnt = n;
sort(od+1,od+n+1,cmp);
for(int i = 2;i <= n;++ i)
if(val[od[i]] == val[od[i-1]])
unionSet(od[i-1],od[i]),--cnt;
for(int i = 1;i <= n;++ i)
if(findSet(i) == i)
who = i,Add(rt[i],val[i],29),Add(rt[0],val[i],29);
LL ans = 0;
while(cnt > 1)
{
for(int i = 1;i <= n;++ i)
{
who = findSet(i); ori = i;
Query(rt[0],rt[who],0,29);
}
for(int i = 1;i <= n;++ i)
{
if(MIN[i] < INF && findSet(i) != findSet(CID[i]))
{
unionSet(i,CID[i]);
ans += MIN[i];
MIN[i] = INF;
--cnt;
}
}
}
Put(ans);
return 0;
}