【考后总结】6 月西安 NOI 模拟赛 1
6.11 冲刺国赛模拟 16#
T3 多边形#
原题:CodeForces-1290F Making Shapes *3500
凸多边形说明合法方案中同一种向量必须连续且多种顺序只算一个,因此直接计算各个向量选择的个数。
设第 个向量选了 个,按照两个方向的正负分,可以写作:
类似。
于是相当于构造出的方案要满足四个数都 ,可以数位 DP,按位枚举 ,在每一位上枚举的复杂度是 ,注意到这里是有 的,因此可能有进位,只能从低位到高位,设 表示枚举到 位,当前在 位的值分别为 ,当前 是否卡上界( 的条件)状态数是 ,这也是复杂度。
(赛时的想法是找互质的然后按照相似来求,和正解偏差很大,数位 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 掌分治#
树上操作很经典,考虑枚举点对 计算删除 时 连通的概率,求和就是答案的期望,在树上这个概率就是 。
放在一个环上,设 两条路径的点数分别是 ,那么只需要保证其中一条没被删除就行了,同时都没被删除需要容斥一下,答案就是 ,这里考场上想的是枚举这些点中 删的时刻再累加,就不太好拓展了。
放在仙人掌上,先建出圆方树,考虑多个环和链结合起来的答案容斥部分并非简单的相乘,考虑固定前面走法不变,长度为 ,增加一个环,此时产生影响是 ,因此增加一个环后这些分母增加,而分母一定在 内,考虑 DP 系数,转移类似背包。由于合并多个环可能相交部分点被算两次,所以钦定当前的环长度不算方点父亲,最终平移一位就行。
做 次不同位置出发,复杂度 。
点击查看代码
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
由于存在一些遮挡关系,雨从一个位置下落是有先后顺序的,满足拓扑关系,拓扑的条件类似扫描线,从左到右扫描,对于两个 坐标上有交的线段,将左端点的较大值作为参考,比较线段上下关系,这样每次找枚举左端点上下连边即可。
这样找到一个拓扑关系,我们可以模拟这个下落的过程,设 表示雨从空中落下最终落在 的最小操作次数,从上到下枚举线段,对于一个 坐标线段 ,如果左低右高,那么首先从 位置落下的雨可以选择落到 ,因此是 值在 取后缀最小值,而在 位置直接落下需要操作一次,因此再区间加 ,这个操作并不好执行。
考虑到操作都是区间,设 ,那么第二个操作就是单点了,而第一个操作实际上就是在 与 都在 内的 会发生改变,以后缀最小值为例,所有 的位置都应该变为 ,但同时也会对后面造成影响。
使用 set
维护 的位置,如果 则向前找 的位置与 抵消,直到 或超过了取最小值的范围,即 。如果此时 还有剩余,这个时候因为 及更前不能取最小值,可以把剩下的数全减在 位置。注意这个只招 的位置实际上是简化了差分数组依次向左改变,略去了 减小又增大本质不变的过程。
同时考虑到前后缀最小值都要维护,分别维护 正负两个部分是比较可行的。注意取最小值和加法两个操作不能颠倒。
另外雨滴可以从非整数的位置落下且不经过任何遮挡,所以横坐标扩大一倍即可。
复杂度考虑势能分析,每次操作使 的绝对值增加 ,而每次取 时将两个 set
维护的信息抵消,相当于绝对值减小且至少减小 ,因此操作次数是 ,算上查询前驱后继的复杂度是 。
点击查看代码
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;
}
6.13 冲刺国赛模拟 18#
作者:SoyTony
出处:https://www.cnblogs.com/SoyTony/p/Simulation_Problems_of_NOI_in_Xian_June_1.html
版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效