Noip模拟98 2021.11.14
T1 构造字符串
看出是图论,但是没有试着用冰茶姬,然后特判\(-1\)的还多了,就直接挂成\(40pts\)了
然后别人分数都很高就直接底垫垫了。。。。对,又一次底垫垫了
转化题意很简单,要求构造一串字典序最小的序列满足一些数相等,一些数不相等
那么直接分情况讨论,先把要求相等的数按照关系分成一堆联通块,然后每一条不相等的关系当做一条边将联通块连起来
不合法的判断只有一条,并没有我考场上想的那么麻烦,直接就如果联通块内有连边就是不合法
然后建完图之后就是变为了只有不相等的部分分(\(z_i=0\))的情况,那么直接贪心的按照编号从小往大扫,每次开个桶找个\(mex\)即可
考场上的\(SB\)做法就是因为没有使用冰茶姬合并相同的关系,而是把相同的和不同的放在了一起处理,这样会导致错误,\(Hack\)很好造,我很蠢。。。
说一下刚开始的时候想的假做法,没有保证字典序最小的时候可以对每一条关系整一个边权,不相等的关系边权为\(1\),相等的为\(0\),然后每个联通块跑一遍\(dfs\)就行了
str
#include<bits/stdc++.h>
#define int long long
using namespace std;FILE *wsn;
auto read=[](){
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*f;
};
const int NN=2005;
int n,m,ans[NN],bin[NN];
bool vis[NN];
struct node{
int a,b,c;
bool operator<(const node&x)const{
return c>x.c;
}
}op[NN];
vector<int>S[NN];
namespace DSU{
int fa[NN];
inline int getfa(int x){return fa[x]=((fa[x]==x)?x:getfa(fa[x]));}
inline void merge(int x,int y){
x=getfa(x); y=getfa(y);
if(x==y) return;if(x>y) swap(x,y);
fa[y]=x;
}
}using namespace DSU;
namespace WSN{
inline short main(){
wsn=freopen("str.in","r",stdin);
wsn=freopen("str.out","w",stdout);
n=read(); m=read();
for(int i=1;i<=n;i++)fa[i]=i;
for(int i=1,a,b,c;i<=m;i++){
a=read(),b=read(),c=read();
if(a>b) swap(a,b);op[i]=node{a,b,c};
}
sort(op+1,op+m+1);
for(int i=1;i<=m&&op[i].c!=0;i++){
int a=op[i].a,b=op[i].b,c=op[i].c;
for(int j=1,x,y;j<=c;j++)
x=a+j-1,y=b+j-1,merge(x,y);
if(b+c<=n) op[++m]=node{a+c,b+c,0};
}
for(int i=m;i>0&&op[i].c==0;i--){
int a=op[i].a,b=op[i].b,c=op[i].c;
if(getfa(a)==getfa(b))return puts("-1"),0;
S[getfa(a)].push_back(getfa(b));
S[getfa(b)].push_back(getfa(a));
}
for(int i=1;i<=n;i++)if(!vis[getfa(i)]){
int mex=0,x=getfa(i); vis[x]=1;
memset(bin,0,sizeof(bin));
for(auto y:S[x])if(y<x) ++bin[ans[y]];
while(bin[mex]) ++mex;
ans[x]=mex;
}
for(int i=1;i<=n;i++)if(getfa(i)!=i)ans[i]=ans[getfa(i)];
for(int i=1;i<=n;i++)printf("%lld ",ans[i]);
puts("");
return 0;
}
}
signed main(){return WSN::main();}
\(\color{white}{过于之菜了,已经连着好几天没有切题了,而且还都不是不会,就是打不对,很郁闷。。。。}\)
T2 寻宝
非常无脑的\(bfs+dfs\),切忌把带着数组的判断条件放在判断下标的前面,否则很可能\(RE\),比如
然后这种送分题都没能切,于是直接底垫垫。。。。。
然后我的打法比较难调,或者说我打的时候比较慌,出了很多zz错误,然后调了好久。。。后面的部分分没来及打,不打挂的话应该能拿\(50\)左右的样子。。。
剩下的思路就不说了,学过信队的,写过最短路的都可以轻松打过(除了我)
treasure
#include<set>
#include<map>
#include<queue>
#include<bitset>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define int long long
using namespace std;FILE *wsn;
int mzs;
#define scanf mzs=scanf
auto read=[](){
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*f;
};
const int NN=50005;
int g[NN],n,m,k,q,in[NN],tot;
char ch[NN];
auto id=[](int x,int y){return (x-1)*m+y;};
struct door{int x1,y1,x2,y2;}d[105];
typedef pair<int,int> PII;
#define fi first
#define se second
#define mpr make_pair
queue<PII> Q;
map<int,PII>mp;
set<int> jump[NN];
bitset<NN>ee[NN];
int dx[5]={0,1,-1,0,0},
dy[5]={0,0,0,1,-1};
bool vis[NN];
inline void walk(int x,int y){
Q.push(mpr(x,y)); vis[id(x,y)]=1;
in[id(x,y)]=++tot;
while(!Q.empty()){
int x=Q.front().fi,y=Q.front().se; Q.pop();
for(int i=1;i<=4;i++){
int xx=x+dx[i],yy=y+dy[i];
if(xx>0&&yy>0&&xx<=n&&yy<=m&&!vis[id(xx,yy)]&&!g[id(xx,yy)]){
Q.push(mpr(xx,yy));
vis[id(xx,yy)]=1; in[id(xx,yy)]=tot;
}
}
}
}
inline void dfs(int x){
vis[x]=1;
for(auto y:jump[x]){
if(vis[y]) continue;
dfs(y);
ee[x]|=ee[y];
}
}
namespace WSN{
inline short main(){
wsn=freopen("treasure.in","r",stdin);
wsn=freopen("treasure.out","w",stdout);
n=read(); m=read(); k=read(); q=read();
for(int i=1;i<=n;i++){
scanf("%s",ch+1);
for(int j=1;j<=m;j++)
g[id(i,j)]=(ch[j]=='#');
}
for(int i=1;i<=k;i++){
int x1=read(),y1=read(),x2=read(),y2=read();
d[i]=door{x1,y1,x2,y2};mp[id(x1,y1)]=mpr(x2,y2);
}
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
if(!vis[id(i,j)]&&!g[id(i,j)]){
walk(i,j);
}
}
}
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
if(mp.find(id(i,j))!=mp.end()){
PII now=mp[id(i,j)];
jump[in[id(i,j)]].insert(in[id(now.fi,now.se)]);
ee[in[id(i,j)]][in[id(now.fi,now.se)]]=1;
}
}
}
for(int i=1;i<=tot;i++){
memset(vis,0,sizeof(vis));
dfs(i);
}
for(int i=1;i<=q;i++){
int x1=read(),y1=read(),x2=read(),y2=read();
int i1=in[id(x1,y1)],i2=in[id(x2,y2)];
if(i1==i2){puts("1");continue;}
if(ee[i1][i2]){puts("1");}
else puts("0");
}
return 0;
}
}
signed main(){return WSN::main();}
T3 序列
李超线段树维护线段
考虑把过\(p\)区间分成右端点在\(p\)的一段最优区间加上左端点在\(p+1\)的一段最优区间
那么每次对答案的统计就变成了固定一个点,找另一个点,这样预计得到\(50\)
那么尝试化简式子
设\(sa_i=\sum_{j=1}^i a_j\),\(sb_i=\sum_{j=1}^ib_j\)
最优即\(\max_{1\leq l\leq r}\{(sa_{r}-sa_{l-1})-k(sb_{r}-sb_{l-1})\}\)
即\(sa_r-k\cdot sb_{r}-\min_{0\leq l<r}\{ksb_{l}-sa_l\}\),离线之后李超树维护直线即可
时间复杂度为\(O(n\log n)\),常数略大,空间复杂度为\(O(n)\)
seq
#include<bits/stdc++.h>
#define int long long
using namespace std; FILE *wsn;
auto read=[](){
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*f;
};
const int NN=2e6+5,inf=1e18;
const int lim=1e6;
int n,m,a[NN],b[NN],ans[NN],lst;
int sufa[NN],sufb[NN],prea[NN],preb[NN];
struct Query{
int p,k,id;
bool operator<(const Query&x)const{
return p==x.p?k<x.k:p<x.p;
}
}qu[NN];
namespace segment_tree{
#define lid (id<<1)
#define rid (id<<1|1)
#define mid ((l+r)>>1)
struct seg{
int k,b;
seg(){k=lim;b=inf;}
seg(int k,int b):k(k),b(b){}
int calc(int x){return k*x+b;}
}tr[NN<<2];
inline void update(seg x,int id=1,int l=-lim,int r=lim){
if(tr[id].calc(mid)>x.calc(mid)) swap(tr[id],x);
if(tr[id].calc(l)>x.calc(l))update(x,lid,l,mid);
if(tr[id].calc(r)>x.calc(r))update(x,rid,mid+1,r);
}
inline int query(int pos,int id=1,int l=-lim,int r=lim){
if(l>pos||r<pos) return inf;
if(l==r) return tr[id].calc(pos);
return min((mid<pos?query(pos,rid,mid+1,r):query(pos,lid,l,mid)),tr[id].calc(pos));
}
}using namespace segment_tree;
namespace WSN{
inline short main(){
wsn=freopen("seq.in","r",stdin);
wsn=freopen("seq.out","w",stdout);
n=read(); m=read();
for(int i=1;i<=n;i++)a[i]=read(),b[i]=read();
for(int i=1;i<=n;i++)prea[i]=prea[i-1]+a[i],preb[i]=preb[i-1]+b[i];
for(int i=n;i;i--)sufa[i]=sufa[i+1]+a[i],sufb[i]=sufb[i+1]+b[i];
for(int o=1,p,k;o<=m;o++) p=read(),k=read(),qu[o]=Query{p,k,o};
sort(qu+1,qu+m+1);
lst=0;
for(int i=1;i<=m;i++){
Query x=qu[i];
for(int j=lst;j<x.p;j++)
update(seg{-preb[j],prea[j]});
ans[x.id]=prea[x.p]-x.k*preb[x.p]-query(x.k);
lst=x.p;
}
memset(tr,0,sizeof(tr));
lst=n+1;
for(int i=m;i;i--){
Query x=qu[i];
for(int j=lst;j>=x.p+1;j--)
update(seg{-sufb[j],sufa[j]});
ans[x.id]+=sufa[x.p+1]-x.k*sufb[x.p+1]-query(x.k);
lst=x.p+1;
}
for(int i=1;i<=m;i++)printf("%lld\n",ans[i]);
return 0;
}
}
signed main(){return WSN::main();}
T4 构树
正在改。。。咕咕咕
改完了,非常难啊,不过还好题解写得好,很明白,于是改的就快一些,同时还有\(fengwu\)的大力帮助%%%
先粘题解,因为是\(markdown\)格式的嘿嘿嘿,因为写得好
(实际上应该是数树)
给了非多项式算法非常多分吧
Algorithm1:枚举
直接枚举所有的树,一共\(n^{n-2}\)种,根据不同枚举方法会带有不同的\(n^w\)
比如直接枚举prufer序列
Algorithm2:状压dp
假装我们的树是一颗以1为根的有根树,模拟展开dfs子树关系的过程
另\(dp_{i,S,j}\)表示以\(i\)为根,子树里已经已经有\(S\)集合的节点,有\(j\)条边相同
然后为一个\(dp_{i,S,j}\)枚举一个儿子\(v\)以及其子树\(T\)进行合并,注意合并顺序防止重复
(比如可以保证\(S-\{i\}\)中最大编号的点<\(T\)中最大编号的点)
复杂度为\(O(3^n n^w)\),\(n\leq 15\)是给这个算法的
Algorithm3:Poly算法
要做多项式复杂度的算法,首先需要一点计数的辅助
Cayley公式:一个完全图有\(n^{n-2}\)棵无根生成树,经典问题prufer序列证明
扩展Cayley公式:被确定边分为大小为\(a_1,a_2,\cdots, a_m\)的连通块,则有\(n^{m-2}\prod {a_i}\)种生成树
证明就不再赘述了
那么我们可以通过 强制一条边出现或者是不出现 来统计边数
通过扩展Cayley公式统计强制的边能够得到多少树
比较Naive的想法,我们可以直接在dp中记录连通块大小、相同边数
每次断开一个连通块,乘上一个\(n\)的系数
强制相同的边,直接合并两个连通块即可
\(dp_{u,i,j}\cdot dp_{v,k,l}\rightarrow dp_{u,i+k,j+l+1}\)
强制不同的边,用不合并的-合并的即可得到
\(nk\cdot dp_{u,i,j}\cdot dp_{v,k,l}\rightarrow dp_{u,i,j+l}\)
\(-dp_{u,i,j}\cdot dp_{v,k,l}\rightarrow dp_{u,i+k,j+l}\)
预计分数:不知道
优化
首先优化连通块大小,可以通过一个经典的转化:
\(size\)作为系数,转化为在这个连通块中选出一个关键点(很显然\(size\)多大,就有多少中选出一个关键点的方案)
这样这一位被压缩到\(0,1\),只需要记录是否在这个连通块中选择了关键点
这样复杂度等同于经典树形背包问题,但是空间略紧张
可以将第3维用拉格朗日插值换掉
设答案数列为\(a_i\),转化为多项式\(A(x)=\sum_{i=0} a_ix^i\)
带入\(x_0=0,1,\cdots ,n\),求得\(A(x_0)\)的数值,然后插值得到多项式
这样第三维就合并可以直接用\(x_0^k\)换掉
其实上我们只需要会那个扩展Cayley公式的部分就可以过掉这道题,再结合一下题解之外的二项式反演
按照如上的\(dp\)可以打出一个\(O(n^2)\)的树形\(dp\),空间复杂度\(O(n^2)\),数组开成\(dp[8001][2][8001]\),是优化的做法
但是不用拉格朗日插值,使用二项式反演
我们把\(dp\)关于相同边数的那一维变成至少有\(i\)条边,这样转移是不变的,可以感性理解一下,毕竟无法用数学方面的理解方式证明
那么我们做完\(dp\)之后算出至少有\(i\)条边和原树同构的方案数,即\(dp[1][1][i]\times \frac{1}{n}\),除以一个\(n\)是因为转移的时候累乘的那个\(n\)最后会多成一个
这样就做完了
tree
#include<bits/stdc++.h>
#define int long long
using namespace std;
namespace AE86{
FILE *wsn;
#define gc() p1==p2 and (p2=(p1=buf)+fread(buf,1,1<<20,stdin),p1==p2)?EOF:*p1++
char buf[1<<20],*p1=buf,*p2=buf;
auto read=[](){
int x=0,f=1;char ch=gc();while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=gc();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=gc();} return x*f;
};
auto write=[](int x,char opt='\n'){
char ch[20];short len=0;if(x<0)x=~x+1,putchar('-');
do{ch[len++]=x%10+(1<<5)+(1<<4);x/=10;}while(x);
for(short i=len-1;i>=0;--i){putchar(ch[i]);}putchar(opt);
};
}using namespace AE86;
const int NN=8005,mod=1e9+7;
int n,dp[NN][2][NN],g[NN],h[NN],v[NN],siz[NN];
struct SNOW{int to,next;};SNOW e[NN<<1]; int head[NN],rp;
auto add=[](int x,int y){e[++rp]=SNOW{y,head[x]};head[x]=rp;e[++rp]=SNOW{x,head[y]};head[y]=rp;};
auto qmo=[](int a,int b,int ans=1){for(;b;b>>=1,a=a*a%mod)if(b&1)ans=ans*a%mod;return ans;};
auto prework=[](){
h[0]=h[1]=1;v[0]=v[1]=1;for(int i=2;i<NN;i++)h[i]=h[i-1]*i%mod;
v[NN-1]=qmo(h[NN-1],mod-2);for(int i=NN-2;i>=2;i--)v[i]=v[i+1]*(i+1)%mod;
};auto C=[](int n,int m){return (n<m||n<0||m<0)?0:h[n]*v[n-m]%mod*v[m]%mod;};
int trans[NN][2][NN],ans[NN];
inline void dfs(int f,int x){
dp[x][0][0]=dp[x][1][0]=1; siz[x]=1;
for(int i=head[x],y=e[i].to;i;i=e[i].next,y=e[i].to)if(f!=y){dfs(x,y);
for(int j=0;j<siz[x];j++)for(int k=0;k<siz[y];k++){
trans[x][1][j+k+1]=(trans[x][1][j+k+1]+dp[x][1][j]*dp[y][0][k]%mod+dp[x][0][j]*dp[y][1][k]%mod)%mod;
trans[x][0][j+k+1]=(trans[x][0][j+k+1]+dp[x][0][j]*dp[y][0][k]%mod)%mod;
trans[x][0][j+k]=(trans[x][0][j+k]+dp[x][0][j]*dp[y][1][k]%mod*n%mod)%mod;
trans[x][1][j+k]=(trans[x][1][j+k]+dp[x][1][j]*dp[y][1][k]%mod*n%mod)%mod;
}
siz[x]+=siz[y];
for(int j=0;j<2;j++)for(int k=0;k<siz[x];k++)dp[x][j][k]=trans[x][j][k],trans[x][j][k]=0;
}
}
namespace WSN{
inline short main(){
wsn=freopen("tree.in","r",stdin);wsn=freopen("tree.out","w",stdout);
n=read(); for(int i=1,u,v;i<n;i++) u=read(), v=read(), add(u,v);
prework();dfs(0,1);for(int i=0;i<n;i++)g[i]=dp[1][1][i]*qmo(n,mod-2)%mod;
for(int i=0;i<n;i++)for(int j=i,bs=1;j<n;j++,bs*=-1)
ans[i]=(ans[i]+bs*C(j,i)*g[j]%mod+mod)%mod;
for(int i=0;i<n;i++)write(ans[i],' ');puts("");
return 0;
}
}
signed main(){return WSN::main();}