总之就是 | ZROI NOIP21冲刺 Day4
「启」
今天 A 因为自己想当然加了个覆盖特殊取值答案的操作挂掉了五十分(
今天就可以说是 DP + Bitset 场了,这对我非常不友好(
下面的 Code 缺省源均为 「V5」.
「A」史上最简洁的题面
这个题吃尽罚时但是还是挂了 50 pts.
因为原题面就很简洁了所以我也不需要简化了:D
「A」题意简述
给定一个 \(n\) 个点 \(m\) 条边的无向图,对于每一个在 \([0,m]\) 内的 \(i\),求有多少中黑白染色方案使得有 \(i\) 条边两端的点都是黑色的。
数据范围:\(n \le 25, 1 \le m \le \frac{n(n-1)}{2}.\)
时间限制:本来是 1s,后来放宽到 2s.
「A」思路简述
因为这个 \(n\) 很小,所以我们考虑用二进制数来表示所有点的状态,第 \(i\) 位是 \(1\) 表明第 \(i\) 个点被染成了黑色。
那么显然一共就有 \(2^n\) 种染色方案,下面来考虑转移状态。
显然两个只相差一个点颜色不同的状态可以互相转移,为了统一,这里转移的方向是从黑色少的状态向黑色多的状态转移,也就是二进制数从小到大转移。
为了方便,我们统一从 x-lowbit(x)
转移到 x
,记 x
和其他点的连通性情况的二进制数为 cnt
.
那么每次新增的两端点都为黑色的边的个数即为 popcount(x-lowbit(x)&cnt)
.
这样我们去递推转移即可,使用了 bitset
以及其函数 .count
,复杂度为 \(O(\dfrac{n2^n}{w}).\)
「A」Code
CI LMT(4e7),MXX(1001);
int n,m,ans[MXX],f[LMT],pos[LMT];
bitset<25> cnt[MXX];
S main()
{
Files();
fr(n),fr(m);
for(int i(0);i<n;++i) pos[1<<i]=i+1;
for(int i(1);i<=m;++i)
{
int x,y;
fr(x),fr(y);cnt[x][y]=1;cnt[y][x]=1;
}
for(int i(1);i<=n;++i) cnt[i]>>=1;
ans[0]=1;
for(int i(1);i<(1<<n);++i)
{
int now(i),p(pos[lowbit(i)]);
bitset<25> temp(now);
temp&=cnt[p];now-=lowbit(i);
f[i]=f[now]+temp.count();++ans[f[i]];
}
for(int i(0);i<=m;++i) fw(ans[i],0);
Heriko Deltana;
}
「B」史上第二简洁的题面
但其实 B 的题面比 A 简洁(x)
「B」题目简述
给定一个长度为 \(n\) 的序列 \(a\),给定 \(m\) 个询问 \(l,r,x\),求 \(a_{l},a_{l+1}\cdots,a_{r}\) 中有多少个数与 \(x\) 互质。
数据范围:\(n,m,a_i,x \le 10^5.\)
时间限制:1s.
「B」思路简述
最一开始以为是数据结构题,后来发现其实是数学题,还用到了莫反(
暴力做法很简单,随便乆能写,保证了我不会爆零(
用柿子来表示本题所求即为:
那么既然是数学题,那就煺柿子:
我们可以在 \(O(\sqrt{n})\) 的时间之内得到每个数的因数,同时可以用线性筛预处理出值域范围内的 \(\mu.\)
为了便于求解,我们把问题离线下来,分成两段求解。
「B」Code
CI MXX(1e5+1),QXX(2e5+1);
int n,m,a[MXX],mu[MXX],prime[MXX],nopr[MXX],tot[MXX],cnt,ans[MXX];
I void Es()
{
mu[1]=1;
for(int i(2);i<=MXX;++i)
{
if(!nopr[i]) prime[++cnt]=i,mu[i]=-1;
for(int j(1);i*prime[j]<=MXX and j<=cnt;++j)
{
nopr[i*prime[j]]=1;
if(i%prime[j]) mu[i*prime[j]]=-mu[i];
else break;
}
}
}
struct Query
{
int opt,id,pos,x;
I bool operator < (const Query &co) const {Heriko pos<co.pos;}
}
q[QXX];
vector<int> fac[MXX];
I void GetFactor(int x)
{
if(fac[x].size()) Heriko;
for(int i(1);i*i<=x;++i)
if(!(x%i))
{
fac[x].push_back(i);
if(i!=x/i) fac[x].push_back(x/i);
}
}
S main()
{
Files();
Es();fr(n),fr(m);int qm(m<<1);
for(int i(1);i<=n;++i) fr(a[i]);
for(int i(1);i<=m;++i)
{
int l,r,x;fr(l),fr(r),fr(x);
q[i*2-1]=(Query){-1,i,l-1,x};
q[i*2]=(Query){1,i,r,x};
}
sort(q+1,q+1+qm);
for(int i(1),j(1);i<=qm;++i)
{
for(;j<=n and j<=q[i].pos;++j)
{
GetFactor(a[j]);
for(auto k:fac[a[j]]) ++tot[k];
}
GetFactor(q[i].x);
for(auto k:fac[q[i].x]) ans[q[i].id]+=q[i].opt*mu[k]*tot[k];
}
for(int i(1);i<=m;++i) fw(ans[i],1);
Heriko Deltana;
}
「C」史上第三简洁的题面
又是一个 DP + Bitset
「C」题目简述
有 \(n\) 个人排成一排,从左到右编号为 \(1...n\),接下来你每次可以指定两个相邻的人战斗,输的一方将会离开队伍,直到最后只有一个人留在队伍里,这个人将会称为最后的赢家。你知道任意两个人打谁会赢,求哪些人可能会成为最后的赢家。
数据范围:\(n \le 2000.\)
时间限制:2s.
「C」思路简述
最一开始以为是要建图,但是后来发现只能让相邻的人干架(
于是就考虑区间 DP,设 \(f(l,r,k)\) 表示 \(k\) 能否在区间 \([l,r]\) 取得胜利,转移的时候枚举 \(k\) 之后再分别从左右枚举转移是否能赢,这样做的复杂度为 \(O(n^4)\),能过 \(60\) pts.
这个时候我们考虑使用 bitset
进行优化,复杂度降至 \(O(\dfrac{n^4}{w})\),但是这样的常数优化显然无法让我们过掉这个题。
于是我们考虑对 DP 的过程复杂度进行进一步的优化,考虑到 \(i\) 的胜利条件只需要左右都让它赢即可,所以我们用空间换时间,把左右的过程分开数组,每次 DP 的时候再互相转移,再加上 bitset
优化,就能把复杂度降至 \(O(\dfrac{n^3}{w}).\)
这样乆能过掉这个题了。
「C」Code
把上文中对应两个
Code
的都放在了这里
「C」60pts
CI MXX(2001);
int n;
bitset<MXX> co[MXX],f[MXX][MXX];
I bool GET()
{
char c(getchar());
while(1)
if(c=='1') Heriko Romanno;
else if(c=='0') Heriko Deltana;
else c=getchar();
}
S main()
{
Files();
fr(n);
for(int i(1);i<=n;++i)
for(int j(1);j<=n;++j)
co[i][j]=GET();
for(int i(1);i<=n;++i) f[i][i][i]=1;
for(int i(1);i<n;++i)
{
f[i][i+1][i]=co[i][i+1];
f[i][i+1][i+1]=co[i+1][i];
}
for(int len(3);len<=n;++len)
{
for(int i(1),j(len);j<=n;++i,++j)
{
for(int k(i);k<=j;++k)
{
int l(0),r(0);
if(k==i) l=1;
else
{
for(int w(i);w<k;++w)
if(co[k][w] and f[i][k-1][w])
{
l=1;break;
}
}
if(k==j) r=1;
else
{
for(int w(j);w>k;--w)
if(co[k][w] and f[k+1][j][w])
{
r=1;break;
}
}
if(l&r) f[i][j][k]=1;
}
}
}
for(int i(1);i<=n;++i)
if(f[1][n][i])
fw(i,0);
Heriko Deltana;
}
「C」100pts
I bool GET()
{
char c(getchar());
while(1)
if(c=='1') Heriko Romanno;
else if(c=='0') Heriko Deltana;
else c=getchar();
}
CI MXX(2001);
int n;
bitset<MXX> f[2][MXX],co[MXX],ans;
S main()
{
Files();
fr(n);
for(int i(1);i<=n;f[0][i][i]=f[1][i][i]=1,++i)
for(int j(1);j<=n;++j)
co[i][j]=GET();
for(int len(0);len<n;++len)
for(int l(1);l+len<=n;++l)
{
int r(l+len);
ans=f[0][l]&f[1][r];
f[1][r][l-1]=(co[l-1]&ans).any();
f[0][l][r+1]=(co[r+1]&ans).any();
}
for(int i(1);i<=n;++i)
if(ans[i])
fw(i,0);
Heriko Deltana;
}
「D」史上第三简洁的题面
这题把我调炸了,写的函数除了
Dijkstra
和CoSet
全都炸了。虽然称作史上第三,但是这个题的题面已经需要我化简了(
「D」题目简述
给出一个 \(n\) 个点 \(m\) 条边的无向图,求对于给定的结点 \(v\) 求单源最短路。
但是有一条不能经过的边,这些边只有在到达其一端的时候才能被知道,求在最优的策略下,最坏情况下多长时间能到达 \(v\)。
数据范围:\(n \le 10^6, m\le 2 \times 10^6.\)
时间限制:7s.
「D」思路简述
首先既然是单源最短路那我们乆写一手 Dijkstra.
接下来考虑怎么才能构造出最坏情况,那么不能走的边乆是从 \(v\) 到其他点的最短路上的一条边。
那我们求得断掉一条边只后的最优策略路径长度 \(val\) 之后,设答案为 \(f(x)\),则有:
「D」Code
template<typename J>
I J Hmax(const J &x,const J &y) {Heriko x>y?x:y;}
CI MXX(4e6+1),NXX(1e6+1);const LL INF(1e17+114514191810);
struct Node
{
int from,nex,to;LL val;bool tag;
I bool operator < (const Node &co) const {Heriko val<co.val;}
}
r[MXX],r2[MXX];int cnt(1),cnt2,head[NXX];
I void Add(int x,int y,LL z)
{
r[++cnt]=(Node){x,head[x],y,z,0};head[x]=cnt;
r[++cnt]=(Node){y,head[y],x,z,0};head[y]=cnt;
}
priority_queue< pair<LL,int> > q;
int n,m,s;
LL dis[NXX],f[NXX],val[NXX];
bool vis[NXX];
I void Dijkstra()
{
mst(vis,0);mst(dis,0x3f);dis[s]=0;q.push(pair<LL,int>(0,s));
while(q.size())
{
int x(q.top().second);q.pop();
if(vis[x]) continue;
vis[x]=1;
for(int i(head[x]);i;i=r[i].nex)
{
int y(r[i].to);
if(dis[y]>dis[x]+r[i].val)
{
dis[y]=dis[x]+r[i].val;
q.push(pair<LL,int>(-dis[y],y));
}
}
}
}
int fa[NXX],dep[NXX];
void DFS(int x,int father)
{
dep[x]=dep[father]+1;vis[x]=1;fa[x]=father;
for(int i(head[x]);i;i=r[i].nex)
{
int y(r[i].to);
if(y==father) continue;
if(vis[y] or dis[y]!=dis[x]+r[i].val) continue;
r[i].tag=1;DFS(y,x);
}
}
int cofa[NXX];
I int Find(int x)
{
while(x!=cofa[x]) x=cofa[x]=cofa[cofa[x]];
Heriko x;
}
I void CoSet(int co)
{
int x(r2[co].from),y(r2[co].to);LL v(r2[co].val);
if(!dep[x] or !dep[y]) Heriko;
x=Find(x),y=Find(y);
while(x!=y)
{
if(dep[x]<dep[y]) swap(x,y);
val[x]=v;cofa[x]=fa[x];x=Find(x);
}
}
I void BFS()
{
while(q.size()) q.pop();
mst(vis,0);mst(f,0x3f);f[s]=0;q.push(pair<LL,int>(0,s));
while(q.size())
{
int x(q.top().second);q.pop();
if(vis[x]) continue;
vis[x]=1;
for(int i(head[x]);i;i=r[i].nex)
{
int y(r[i].to);
if(vis[y]) continue;
if(f[y]>Hmax(val[y],f[x]+r[i].val))
{
f[y]=Hmax(val[y],f[x]+r[i].val);
q.push(pair<LL,int>(-f[y],y));
}
}
}
}
I LL Calc(int x) {Heriko dis[r[x].from]+dis[r[x].to]+r[x].val;}
S main()
{
Files();
fr(n),fr(m),fr(s);
for(int i(1);i<=n;++i) cofa[i]=i;
for(int i(1);i<=m;++i)
{
int x,y;LL z;
fr(x),fr(y),fr(z);
Add(x,y,z);
}
Dijkstra();mst(vis,0);DFS(s,0);
for(int i(2);i<cnt;i+=2)
{
if(r[i].tag or r[i^1].tag) continue;
r2[++cnt2]=r[i];r2[cnt2].val=Calc(i);
}
sort(r2+1,r2+1+cnt2);mst(val,0x3f);
for(int i(1);i<=cnt2;++i) CoSet(i);
for(int i(1);i<=n;++i) val[i]-=dis[i];
BFS();
for(int i(1);i<=n;++i) fw(f[i]>INF/10?-1:f[i],0);
Heriko Deltana;
}
「终」
虽然但是,没啥好说的。