【考后总结】6 月西安 NOI 模拟赛 1
6.11 冲刺国赛模拟 16
T3 多边形
原题:CodeForces-1290F Making Shapes *3500
凸多边形说明合法方案中同一种向量必须连续且多种顺序只算一个,因此直接计算各个向量选择的个数。
设第 \(i\) 个向量选了 \(c_i\) 个,按照两个方向的正负分,可以写作:
\(y\) 类似。
于是相当于构造出的方案要满足四个数都 \(\le m\),可以数位 DP,按位枚举 \(c\),在每一位上枚举的复杂度是 \(O(2^n)\),注意到这里是有 \(|x|,|y|\le \omega=4\) 的,因此可能有进位,只能从低位到高位,设 \(f(i,px,py,nx,ny,limx,limy)\) 表示枚举到 \(2^i\) 位,当前在 \(2^i\) 位的值分别为 \(px,py,nx,ny\),当前 \(x,y\) 是否卡上界(\(\le m\) 的条件)状态数是 \(O((n\omega)^42^n\log m)\),这也是复杂度。
(赛时的想法是找互质的然后按照相似来求,和正解偏差很大,数位 DP 可以这样使用也蛮高级的)
点击查看代码
int n,m;
int x[maxn],y[maxn];
int f[maxlog][maxw][maxw][maxw][maxw][2][2];
bool vis[maxlog][maxw][maxw][maxw][maxw][2][2];
int dfs(int now,int x1,int x2,int y1,int y2,bool limx,bool limy){
if(now==30) return !x1&&!x2&&!y1&&!y2&&limx&&limy;
if(vis[now][x1][x2][y1][y2][limx][limy]) return f[now][x1][x2][y1][y2][limx][limy];
vis[now][x1][x2][y1][y2][limx][limy]=true;
int res=0;
for(int s=0;s<(1<<n);++s){
int tmpx1=x1,tmpx2=x2,tmpy1=y1,tmpy2=y2;
for(int i=1;i<=n;++i){
if(s&(1<<i-1)){
if(x[i]>0) tmpx1+=x[i];
else tmpx2-=x[i];
if(y[i]>0) tmpy1+=y[i];
else tmpy2-=y[i];
}
}
if((tmpx1^tmpx2)&1||(tmpy1^tmpy2)&1) continue;
bool chkm=(m>>now)&1,chkx=tmpx1&1,chky=tmpy1&1;
res=(res+dfs(now+1,tmpx1/2,tmpx2/2,tmpy1/2,tmpy2/2,chkm?(!chkx||limx):(!chkx&&limx),chkm?(!chky||limy):(!chky&&limy)))%mod;
}
return f[now][x1][x2][y1][y2][limx][limy]=res;
}
int ans;
int main(){
freopen("polygon.in","r",stdin);
freopen("polygon.out","w",stdout);
n=read(),m=read();
for(int i=1;i<=n;++i){
x[i]=read(),y[i]=read();
}
ans=(dfs(0,0,0,0,0,true,true)-1+mod)%mod;
printf("%d\n",ans);
return 0;
}
6.12 冲刺国赛模拟 17
T1 掌分治
树上操作很经典,考虑枚举点对 \((u,v)\) 计算删除 \(u\) 时 \((u,v)\) 连通的概率,求和就是答案的期望,在树上这个概率就是 \(\dfrac{1}{\mathrm{dist}(u,v)}\)。
放在一个环上,设 \(u\to v\) 两条路径的点数分别是 \(a,b\),那么只需要保证其中一条没被删除就行了,同时都没被删除需要容斥一下,答案就是 \(\dfrac{1}{a}+\dfrac{1}{b}-\dfrac{1}{a+b-1}\),这里考场上想的是枚举这些点中 \(u\) 删的时刻再累加,就不太好拓展了。
放在仙人掌上,先建出圆方树,考虑多个环和链结合起来的答案容斥部分并非简单的相乘,考虑固定前面走法不变,长度为 \(l\),增加一个环,此时产生影响是 \(\dfrac{1}{l+a}+\dfrac{1}{l+b}-\dfrac{1}{l+a+b-1}\),因此增加一个环后这些分母增加,而分母一定在 \([1,n]\) 内,考虑 DP 系数,转移类似背包。由于合并多个环可能相交部分点被算两次,所以钦定当前的环长度不算方点父亲,最终平移一位就行。
做 \(n\) 次不同位置出发,复杂度 \(O(n^3)\)。
点击查看代码
int n,m;
int inv[maxn];
struct edge{
int to,nxt;
}e[maxn<<2];
int head[maxn],cnt;
inline void add_edge(int u,int v){
e[++cnt].to=v,e[cnt].nxt=head[u],head[u]=cnt;
e[++cnt].to=u,e[cnt].nxt=head[v],head[v]=cnt;
}
int dfn[maxn],low[maxn],dfncnt;
int st[maxn],top;
int tot;
vector<int> E[maxn<<1];
unordered_map<int,int> mp[maxn];
void Tarjan(int u){
dfn[u]=++dfncnt,low[u]=dfn[u];
st[++top]=u;
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].to;
if(!dfn[v]){
Tarjan(v);
low[u]=min(low[u],low[v]);
if(low[v]>=dfn[u]){
++tot;
int id=0;
while(st[top]!=v){
E[st[top]].push_back(tot),E[tot].push_back(st[top]);
mp[tot-n][st[top]]=++id;
--top;
}
E[v].push_back(tot),E[tot].push_back(v);
mp[tot-n][v]=++id;
--top;
E[u].push_back(tot),E[tot].push_back(u);
mp[tot-n][u]=++id;
}
}
else low[u]=min(low[u],dfn[v]);
}
}
int f[maxn][maxn];
void dfs(int u,int fa){
if(u<=n){
f[u][0]=1;
for(int v:E[u]){
if(v==fa) continue;
dfs(v,u);
}
}
else{
if((int)E[u].size()==2){
for(int v:E[u]){
if(v==fa) continue;
dfs(v,u);
for(int i=0;i+1<n;++i) f[fa][i+1]=(f[fa][i+1]+f[v][i])%mod;
}
}
else{
int id=mp[u-n][fa];
int now=0;
for(int v:E[u]){
++now;
if(v==fa) continue;
dfs(v,u);
int k1=abs(now-id),k2=(int)E[u].size()-k1;
for(int i=0;i+k1<n;++i) f[fa][i+k1]=(f[fa][i+k1]+f[v][i])%mod;
for(int i=0;i+k2<n;++i) f[fa][i+k2]=(f[fa][i+k2]+f[v][i])%mod;
for(int i=0;i+k1+k2-1<n;++i) f[fa][i+k1+k2-1]=(f[fa][i+k1+k2-1]-f[v][i]+mod)%mod;
}
}
}
}
int ans;
int main(){
freopen("cactus.in","r",stdin);
freopen("cactus.out","w",stdout);
n=read(),m=read();
inv[1]=1;
for(int i=2;i<=n;++i) inv[i]=1ll*(mod-mod/i)*inv[mod%i]%mod;
for(int i=1;i<=m;++i){
int u=read(),v=read();
add_edge(u,v);
}
tot=n;
Tarjan(1);
for(int i=1;i<=n;++i){
memset(f,0,sizeof(f));
dfs(i,0);
for(int j=0;j<n;++j) ans=(ans+1ll*f[i][j]*inv[j+1]%mod+mod)%mod;
}
printf("%d\n",ans);
return 0;
}
T3 下大雨
原题:洛谷-P6256 ICPC 2019 WF Directing Rainfall
由于存在一些遮挡关系,雨从一个位置下落是有先后顺序的,满足拓扑关系,拓扑的条件类似扫描线,从左到右扫描,对于两个 \(x\) 坐标上有交的线段,将左端点的较大值作为参考,比较线段上下关系,这样每次找枚举左端点上下连边即可。
这样找到一个拓扑关系,我们可以模拟这个下落的过程,设 \(f_i\) 表示雨从空中落下最终落在 \((i,0)\) 的最小操作次数,从上到下枚举线段,对于一个 \(x\) 坐标线段 \([l,r]\),如果左低右高,那么首先从 \(i\) 位置落下的雨可以选择落到 \([l,i]\),因此是 \(f\) 值在 \([l,r]\) 取后缀最小值,而在 \([l+1,r]\) 位置直接落下需要操作一次,因此再区间加 \(1\),这个操作并不好执行。
考虑到操作都是区间,设 \(g_i=f_i-f_{i-1}\),那么第二个操作就是单点了,而第一个操作实际上就是在 \(i-1\) 与 \(i\) 都在 \([l,r]\) 内的 \(g\) 会发生改变,以后缀最小值为例,所有 \(g_i<0\) 的位置都应该变为 \(0\),但同时也会对后面造成影响。
使用 set
维护 \(g_i\neq 0\) 的位置,如果 \(g_i<0\) 则向前找 \(g_j>0\) 的位置与 \(g_i\) 抵消,直到 \(g_i=0\) 或超过了取最小值的范围,即 \(g_{l}\)。如果此时 \(g_i\) 还有剩余,这个时候因为 \(g_l\) 及更前不能取最小值,可以把剩下的数全减在 \(g_l\) 位置。注意这个只招 \(g_j>0\) 的位置实际上是简化了差分数组依次向左改变,略去了 \(g_j=0\) 减小又增大本质不变的过程。
同时考虑到前后缀最小值都要维护,分别维护 \(g\) 正负两个部分是比较可行的。注意取最小值和加法两个操作不能颠倒。
另外雨滴可以从非整数的位置落下且不经过任何遮挡,所以横坐标扩大一倍即可。
复杂度考虑势能分析,每次操作使 \(g\) 的绝对值增加 \(2\),而每次取 \(\min\) 时将两个 set
维护的信息抵消,相当于绝对值减小且至少减小 \(2\),因此操作次数是 \(O(n)\),算上查询前驱后继的复杂度是 \(O(n\log n)\)。
点击查看代码
int n,L,R;
struct Segment{
int id,x1,y1,x2,y2;
Segment()=default;
Segment(int id_,int x1_,int y1_,int x2_,int y2_):id(id_),x1(x1_),y1(y1_),x2(x2_),y2(y2_){}
bool operator<(const Segment& rhs)const{
if(x1<rhs.x1) return 1ll*(y2-y1)*(rhs.x1-x1)+1ll*y1*(x2-x1)<1ll*rhs.y1*(x2-x1);
else return 1ll*y1*(rhs.x2-rhs.x1)<1ll*(rhs.y2-rhs.y1)*(x1-rhs.x1)+1ll*rhs.y1*(rhs.x2-rhs.x1);
}
}Seg[maxn];
struct Data{
int id,val;
Data()=default;
Data(int id_,int val_):id(id_),val(val_){}
bool operator<(const Data& rhs)const{
return val>rhs.val;
}
};
set<Segment> S;
priority_queue<Data> Q;
vector<int> E[maxn];
int deg[maxn];
queue<int> q;
multiset<pii> Sp,Sn;
int main(){
freopen("rain.in","r",stdin);
freopen("rain.out","w",stdout);
L=read()*2,R=read()*2,n=read();
for(int i=1;i<=n;++i){
int x1=read()*2,y1=read(),x2=read()*2,y2=read();
if(x1>x2) swap(x1,x2),swap(y1,y2);
Seg[i]=Segment(i,x1,y1,x2,y2);
}
sort(Seg+1,Seg+n+1,[&](Segment A,Segment B){
return A.x1<B.x1;
});
for(int i=1;i<=n;++i) Seg[i].id=i;
for(int i=1;i<=n;++i){
while(!Q.empty()&&Q.top().val<Seg[i].x1){
S.erase(Seg[Q.top().id]);
Q.pop();
}
auto it=S.lower_bound(Seg[i]);
if(it!=S.end()){
E[(*it).id].push_back(i);
++deg[i];
}
if(it!=S.begin()){
--it;
E[i].push_back((*it).id);
++deg[(*it).id];
}
S.insert(Seg[i]);
Q.push(Data(i,Seg[i].x2));
}
for(int i=1;i<=n;++i){
if(!deg[i]) q.push(i);
}
Sp.insert(mp(R+1,inf));
Sn.insert(mp(L,inf));
while(!q.empty()){
int u=q.front();
q.pop();
if(Seg[u].y1<Seg[u].y2){
auto it1=Sn.lower_bound(mp(Seg[u].x2+1,-inf));
if(it1!=Sn.begin()){
--it1;
while((*it1).fir>=Seg[u].x1+1){
int val=(*it1).sec;
auto it2=Sp.lower_bound(mp((*it1).fir+1,-inf));
if(it2!=Sp.begin()){
--it2;
while(val&&(*it2).fir>=Seg[u].x1){
if((*it2).sec>val){
Sp.insert(mp((*it2).fir,(*it2).sec-val));
Sp.erase(it2);
val=0;
break;
}
else{
val-=(*it2).sec;
auto tmp=it2;
if(it2!=Sp.begin()){
--it2;
Sp.erase(tmp);
}
else{
Sp.erase(tmp);
break;
}
}
}
}
if(val) Sn.insert(mp(Seg[u].x1,val));
auto tmp=it1;
if(it1!=Sn.begin()){
--it1;
Sn.erase(tmp);
}
else{
Sn.erase(tmp);
break;
}
}
}
Sp.insert(mp(Seg[u].x1+1,1));
Sn.insert(mp(Seg[u].x2+1,1));
}
else{
auto it1=Sp.lower_bound(mp(Seg[u].x1,-inf));
while(it1!=Sp.end()&&(*it1).fir<=Seg[u].x2){
int val=(*it1).sec;
auto it2=Sn.lower_bound(mp((*it1).fir,-inf));
while(it2!=Sn.end()&&val&&(*it2).fir<=Seg[u].x2+1){
if((*it2).sec>val){
Sn.insert(mp((*it2).fir,(*it2).sec-val));
Sn.erase(*it2);
val=0;
break;
}
else{
val-=(*it2).sec;
auto tmp=it2;
++it2;
Sn.erase(tmp);
if(it2==Sn.end()) break;
}
}
if(val) Sp.insert(mp(Seg[u].x2+1,val));
auto tmp=it1;
++it1;
Sp.erase(tmp);
}
Sp.insert(mp(Seg[u].x1,1));
Sn.insert(mp(Seg[u].x2,1));
}
for(int v:E[u]){
--deg[v];
if(!deg[v]) q.push(v);
}
}
for(auto it=Sn.begin();it!=Sn.end();++it){
Sp.insert(mp((*it).fir,-(*it).sec));
}
int ans=inf,sum=inf,now=-inf;
Sp.insert(mp(L,0));
for(auto it=Sp.begin();it!=Sp.end();++it){
if((*it).fir!=now&&now>=L&&now<=R) ans=min(ans,sum);
now=(*it).fir,sum+=(*it).sec;
}
printf("%d\n",ans);
return 0;
}