20联赛集训day3 题解
因为某些原因晚了半小时打,然后就疯狂挂题,从前二(或许?)挂到了倒一
A
可以考虑哈希。这里可以给每个值随机一个哈希值,合并操作定义为异或。
考试的时候写的N模好像 T 飞了。。事实上这个数据一模数就能过
#pragma GCC optimize("Ofast")
#include <bits/stdc++.h>
#define fi first
#define se second
#define db double
#define U unsigned
#define P std::pair<int,int>
#define LL long long
#define pb push_back
#define MP std::make_pair
#define all(x) x.begin(),x.end()
#define CLR(i,a) memset(i,a,sizeof(i))
#define FOR(i,a,b) for(int i = a;i <= b;++i)
#define ROF(i,a,b) for(int i = a;i >= b;--i)
#define DEBUG(x) std::cerr << #x << '=' << x << std::endl
inline char nc(){
#define SIZE 1000000+3
static char buf[SIZE],*p1 = buf+SIZE,*p2 = buf+SIZE;
if(p1 == p2){
p1 = buf;p2 = buf+fread(buf,1,SIZE,stdin);
if(p1 == p2) return -1;
}
return *p1++;
#undef SIZE
}
template <typename T>
inline void read(T &x){
x = 0;int flag = 0;char ch = nc();
while(!isdigit(ch)){
if(ch == '-') flag = 1;
ch = nc();
}
while(isdigit(ch)){
x = (x<<1) + (x<<3) + (ch^'0');
ch = nc();
}
if(flag) x = -x;
}
const int MAXN = 3e5 + 5;
const int H = 2;
const int mod[] = {0,998244353,(int)1e9+7,(int)1e9+9,666623333,1004535809};
const int MAXM = 17;
std::mt19937 g(time(NULL));
inline int qpow(int a,int n,int ha){
int res = 1;
while(n){
if(n & 1) res = 1ll*res*a%ha;
a = 1ll*a*a%ha;
n >>= 1;
}
return res;
}
struct Node{
int t[H+1];
Node(){FOR(i,1,H) t[i] = 1;}
inline void gen(int id){
//FOR(i,1,H) t[i] = id;//g()%mod[i];
FOR(i,1,H) t[i] = g()%mod[i];
}
inline Node operator + (const Node &x) const {
Node res;
FOR(i,1,H) res.t[i] = 1ll*t[i]*x.t[i]%mod[i];
return res;
}
inline Node inv(){
Node res;
FOR(i,1,H) res.t[i] = qpow(t[i],mod[i]-2,mod[i]);
return res;
}
inline bool operator == (const Node &x) const {
FOR(i,1,H) if(x.t[i] != t[i]) return 0;
return 1;
}
inline void debug(){
FOR(i,1,H) printf("%d ",t[i]);puts("");
}
}val[MAXN],chk[MAXN],ival[MAXN];
int M;
struct BIT{
#define lowbit(x) ((x)&(-(x)))
Node tree[MAXN];
inline void add(int pos,Node x){
while(pos <= M){
tree[pos] = tree[pos]+x;
pos += lowbit(pos);
}
}
inline Node query(int pos){
Node res;
while(pos){
res = res+tree[pos];
pos -= lowbit(pos);
}
return res;
}
}bit;
std::vector<int> G[MAXN];
int a[MAXN],n,q;
int dfn[MAXN],sz[MAXN],dep[MAXN];
int f[MAXN][MAXM+1],ts;
Node sm[MAXN];
inline void dfs(int v,int fa=0){
sm[v] = sm[fa]+val[a[v]];
sz[v] = 1;f[v][0] = fa;
dfn[v] = ++ts;dep[v] = dep[fa]+1;
FOR(i,1,MAXM) f[v][i] = f[f[v][i-1]][i-1];
for(auto x:G[v]){
if(x == fa) continue;
dfs(x,v);sz[v] += sz[x];
}
}
inline int lca(int x,int y){
if(dep[x] != dep[y]){
if(dep[x] < dep[y]) std::swap(x,y);
int d = dep[x]-dep[y];
FOR(i,0,MAXM) if((d>>i)&1) x = f[x][i];
}
if(x == y) return x;
ROF(i,MAXM,0){
if(f[x][i] == f[y][i]) continue;
x = f[x][i],y = f[y][i];
}
return f[x][0];
}
inline Node query(int x){// 查询x->1
if(x == 0) return Node();
return sm[x]+bit.query(dfn[x]);
}
inline void Solve(){
ts = 0;
read(n);read(q);
// n = q = 100000;
M = n+5;
// FOR(i,1,n) a[i] = 1+g()%n;
FOR(i,1,n) read(a[i]);
FOR(i,2,n){
int u,v;
// u = i;v = 1+g()%(i-1);
read(u);read(v);
// scanf("%d%d",&u,&v);
G[u].pb(v);G[v].pb(u);
}
FOR(i,1,n) val[i].gen(i),ival[i] = val[i].inv();
FOR(i,1,n) chk[i] = chk[i-1]+val[i];
dfs(1);
FOR(i,1,q){
int opt,x,y;
// opt = 1+g()%2;
// x = 1+g()%n;y = 1+g()%n;
read(opt);read(x);read(y);
// scanf("%d%d%d",&opt,&x,&y);
if(opt == 1){
int l = lca(x,y);
Node res = query(x)+query(y);
Node i = query(f[l][0]).inv();
res = res+i;res = res+i;
res = res+ival[a[l]];
int len = dep[x]+dep[y]-2*dep[l]+1;
puts(res==chk[len]?"Yes":"No");
}
else{
int l = dfn[x],r = dfn[x]+sz[x]-1;
bit.add(l,ival[a[x]]);
bit.add(r+1,val[a[x]]);
a[x] = y;
bit.add(l,val[a[x]]);
bit.add(r+1,ival[a[x]]);
}
}
FOR(i,1,n) G[i].clear();
FOR(i,0,M) bit.tree[i] = Node();
}
int main(){
int T;read(T);
while(T--) Solve();
return 0;
}
B
sb题,但是因为没时间了所以没有仔细思考。
我们先不管 \(T_i\) 的限制看看怎么做:
显然是要建分层图,我们只需要建出对应的地面的点,单车的点,车站的点,地铁的点,对题意模拟建图即可。
发现这个 \(T_i\) 的限制形如限制某条边如果只能在 \(\bmod T_i = x_i\) 的时间通过。首先贪心地想我们肯定不会战略停留一会儿在走,所以我们还是跑最短路,对有这种特殊限制的边,额外计算等待时间即可。(我猜这个只能跑 dij)
注意 \(T_i\) 是没有限制 \(\leq 10^9\) 的,相关变量要开 long long。
#include <bits/stdc++.h>
#define fi first
#define se second
#define db double
#define U unsigned
#define P std::pair<LL,int>
#define LL long long
#define pb push_back
#define MP std::make_pair
#define all(x) x.begin(),x.end()
#define CLR(i,a) memset(i,a,sizeof(i))
#define FOR(i,a,b) for(int i = a;i <= b;++i)
#define ROF(i,a,b) for(int i = a;i >= b;--i)
#define DEBUG(x) std::cerr << #x << '=' << x << std::endl
const int MAXN = 2e6 + 5;
struct Edge{
int to;LL w;int nxt;LL T,ts;
}e[MAXN<<1];
int head[MAXN],cnt;
inline void add(int u,int v,LL w,LL T=0,LL ts=0){
e[++cnt] = (Edge){v,w,head[u],T,ts};head[u] = cnt;
}
LL dis[MAXN];
bool used[MAXN];
int N;
int G[MAXN],B[MAXN],S[MAXN],T[MAXN];
inline void dij(){
std::priority_queue<P,std::vector<P>,std::greater<P> > q;
FOR(i,0,N) dis[i] = 1e18;
q.push(MP(dis[G[1]]=0,G[1]));
while(!q.empty()){
int v = q.top().se;LL d = q.top().fi;q.pop();
if(d != dis[v]) continue;
if(used[v]) continue;
used[v] = 1;
for(int i = head[v];i;i = e[i].nxt){
LL gx = dis[v]+e[i].w;
if(e[i].T){
// DEBUG(dis[v]);DEBUG(e[i].ts);DEBUG(e[i].T);
// DEBUG((e[i].ts-dis[v]%e[i].T+e[i].T)%e[i].T);
gx += (e[i].ts+e[i].T-dis[v]%e[i].T)%e[i].T;
}
if(dis[e[i].to] > gx){
dis[e[i].to] = gx;
q.push(MP(dis[e[i].to],e[i].to));
}
}
}
}
int n,r,s,x;
int ee[MAXN],c[MAXN];
/*
G: 地面
B: 共享单车
S: 车站
T: 地铁
*/
int v[MAXN],l[MAXN];
signed main(){
scanf("%d%d%d%d",&n,&r,&s,&x);
FOR(i,1,n) scanf("%d",ee+i);
FOR(i,1,n) scanf("%d",c+i);
FOR(i,1,n) G[i] = ++N,B[i] = ++N,S[i] = ++N;
FOR(i,1,n) add(G[i],B[i],x),add(B[i],G[i],0);
FOR(i,1,n) add(G[i],S[i],ee[i]);//,add(S[i],G[i],ee[i]);
FOR(i,1,r){
int u,v,w;scanf("%d%d%d",&u,&v,&w);
add(B[u],B[v],w);add(B[v],B[u],w);
}
FOR(iii,1,s){
int k;scanf("%d",&k);
FOR(i,1,k) scanf("%d%d",v+i,l+i);scanf("%d",v+k+1);
LL tt;scanf("%lld",&tt);
FOR(i,1,k) T[v[i]] = ++N;
if(v[k+1] != v[1]) T[v[k+1]] = ++N;
LL sm = 0;
FOR(i,1,k){
add(T[v[i]],T[v[i+1]],l[i],tt,sm);
(sm += l[i]) %= tt;
}
if(v[1] == v[k+1]) sm = 0;
ROF(i,k,1){
add(T[v[i+1]],T[v[i]],l[i],tt,sm);
(sm += l[i]) %= tt;
}
FOR(i,1,k) add(S[v[i]],T[v[i]],0),add(T[v[i]],S[v[i]],c[v[i]]),add(T[v[i]],G[v[i]],ee[v[i]]);
if(v[k+1] != v[1]) add(S[v[k+1]],T[v[k+1]],0),add(T[v[k+1]],S[v[k+1]],c[v[k+1]]),add(T[v[k+1]],G[v[k+1]],ee[v[k+1]]);
}
dij();
FOR(i,1,n) printf("%lld ",dis[G[i]]);puts("");
return 0;
}
C
是个神仙剪枝题。
首先暴力是一分没有的。
这个问题有一个经典剪枝是每次选尽可能大的一些数,如果还是不能大于原数就返回;选尽可能小的数,如果大于原数也返回。这样就能获得 \(10\) 分了。
一个很 naive 的想法是求个逆元然后做背包:但是发现这样会犯单面错误:它会统计所有合法的方案,但是也会错误统计部分不合法方案。所以我们可以在每一层用这个背包来判断是否有往下搜索的必要,就可以拿到 \(50\) 分的好成绩了。(搜索剪枝优化的时候要考虑一些单面错误的东西。。)
神仙正解:我们先倒着考虑,将目标消成 \(0\)。
首先我们发现直接对通分后的值域做背包是对的,但是值域过大。于是我们考虑只取 \(\leq \sqrt n\) 的所有数的 \(\text{lcm}\)(大概是 \(2^3 \times 3^2 \times 5 \times 7 = 2520\),对所有能被通分成这个数的数都拿出来做背包。
然后剩下的数都是存在且仅存在一个 \(> \sqrt n\) 的因数 \(p^c\)。首先我们发现 \(>\sqrt n\) 的质数不能要(消不掉),所以我们考虑所有这样的因数,只有 \(\{11,13,16,17,19,23,25,27,29,31,37\}\)。我们将剩下的所有数按照是某个数的倍数分类,对每一类内部先搜出所有方案,然后考虑 dfs。
这里有一个很好的剪枝是发现如果我们考虑完了 \(p^c\) 后分母如果还是 \(p^c\) 的倍数,那么这个方案一定不合法(因为消不掉),所以加上这个剪枝后跑的飞快(我的程序极限数据 70ms)
那么这个剪枝为什么是对的呢?
首先我们注意到 \((a,b) = 1\Rightarrow (a+b,ab)=1\)
证明:
我们考虑如果一个埃及分数加上后能消掉分母的一些东西那么一定满足加上后能约分,设这个算式是 \(\frac{a}{b}+\frac{1}{c}\),能否约分取决于 \((bc,ac+b)\)。
如果做完了 \(p^c\) 的所有倍数后,将 \(\frac{a}{b}\) 裂出来分母仅包含 \(p^c\) ,可以得到 \((b,c)=1\),注意到 \((a,b)=1\),所以
所以一定消不掉。
#include <bits/stdc++.h>
#define fi first
#define se second
#define db double
#define U unsigned
#define P std::pair<int,int>
#define LL long long
#define pb push_back
#define MP std::make_pair
#define all(x) x.begin(),x.end()
#define CLR(i,a) memset(i,a,sizeof(i))
#define FOR(i,a,b) for(int i = a;i <= b;++i)
#define ROF(i,a,b) for(int i = a;i >= b;--i)
#define DEBUG(x) std::cerr << #x << '=' << x << std::endl
const int B = 2520;
const int MAXN = 30+5;
inline LL gcd(LL x,LL y){
return !y ? x : gcd(y,x%y);
}
struct Node{
LL x,y;// y/x
Node(LL y=0,LL x=0) : x(x),y(y) {}
inline Node operator + (const Node &t) const {
LL g = gcd(x*t.x,y*t.x+x*t.y);
return Node((y*t.x+x*t.y)/g,x*t.x/g);
}
inline Node operator - (const Node &t) const {
LL g = gcd(x*t.x,y*t.x-x*t.y);
return Node((y*t.x-x*t.y)/g,x*t.x/g);
}
inline bool operator < (const Node &t) const {
return y*t.x < x*t.y;
}
};
LL f[MAXN][2*B+3];
// 选了i个,答案在分母是B的情况下是j
const int p[] = {0,11,13,16,17,19,23,25,27,29,31,37};
std::vector<int> G[MAXN];
std::vector<std::pair<Node,int> > ways[MAXN];
LL ans = 0;
int tot = 11,m,n;
Node req;
inline void work(int id){
if(G[id].empty()) return;
int sz = G[id].size();
FOR(S,1,(1<<sz)-1){
Node res(0,1);
FOR(i,0,sz-1){
if((S>>i)&1){
res = res+Node(1,G[id][i]);
}
}
if(req < res) continue;
if(!((res-req).x%p[id])) continue;
ways[id].pb(MP(res,__builtin_popcount(S)));
}
}
// y/x
inline void dfs(int dep,Node num,int cnt){
if(cnt > m) return;
if(cnt == m){
if(num.y == 0) ans++;
return;
}
if(dep == tot+1){
if(B%num.x) return;
int v = num.y*(B/num.x);
ans += f[m-cnt][v];
return;
}
dfs(dep+1,num,cnt);
for(auto x:ways[dep]){
if(num < x.fi) continue;
Node nxt = num-x.fi;
// if(!(nxt.x%p[dep])) continue;
dfs(dep+1,nxt,cnt+x.se);
}
}
int main(){
int x,y;scanf("%d%d%d%d",&m,&n,&x,&y);
int g = gcd(x,y);
x /= g;y /= g;req = Node(x,y);
f[0][0] = 1;
FOR(x,1,n){
if(B%x) continue;
int v = B/x;
// DEBUG(v);
// f[i][j] = f[i-1][j-v]
ROF(i,m,1){
ROF(j,2*B,v){
f[i][j] += f[i-1][j-v];
}
}
}
FOR(x,1,n){
if(!(B%x)) continue;
bool flag =0;
FOR(i,1,tot){
if(!(x%p[i])){
G[i].pb(x);flag=1;
break;
}
}
}
FOR(i,1,tot) work(i);
dfs(1,req,0);
printf("%lld\n",ans);
return 0;
}