NOIP模擬97(多校30)
阿新 • • 發佈:2021-11-14
「構造字串· 尋寶 ·序列 ·構樹 」on 11.14
。
T1 構造字串
解題思路
不算特別難的題,但是有一點細節。。。
首先需要並茶几縮一下點,然後判斷一下是否合法,由於我們需要字典序最小的,因此我們應當保證一個聯通塊中標號較小的點為根節點。
那麼對於所有不能夠相等的標號對,我們再標號較大的點記下來標號較小的點的限制,然後從前往後掃一遍取 \(mex\) 值就好了。
code
#include<bits/stdc++.h> #define int long long #define ull unsigned long long #define f() cout<<"RP++"<<endl using namespace std; inline int read() { int x=0,f=1; char ch=getchar(); while(ch>'9'||ch<'0'){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 N=1e3+10; int n,m,fa[N],ans[N]; struct Node{int x,y,z;}s[N]; vector<int> v[N]; bitset<N> bit; int find(int x) { if(fa[x]==x) return x; return fa[x]=find(fa[x]); } #undef int int main() { #define int long long freopen("str.in","r",stdin); freopen("str.out","w",stdout); n=read(); m=read(); for(int i=1;i<=n;i++) fa[i]=i; for(int i=1,x,y,z;i<=m;i++) { x=read(); y=read(); z=read(); s[i]=(Node){x,y,z}; for(int j=1;j<=z;j++) if(find(x+j-1)!=find(y+j-1)) if(find(y+j-1)>find(x+j-1)) fa[find(y+j-1)]=find(x+j-1); else fa[find(x+j-1)]=find(y+j-1); } for(int i=1;i<=m;i++) { int p1=s[i].x+s[i].z,p2=s[i].y+s[i].z; if(p1>n||p2>n) continue; if(find(p1)==find(p2)) printf("-1"),exit(0); if(find(p1)>find(p2)) swap(p1,p2); v[find(p2)].push_back(find(p1)); } for(int i=1,col;i<=n;i++) { if(find(i)!=i) continue; bit.reset(); for(auto it:v[i]) bit[ans[it]]=true; col=0; while(bit[col]) col++; ans[i]=col; } for(int i=1;i<=n;i++) printf("%lld ",ans[find(i)]); return 0; }
T2 尋寶
解題思路
簽到題。
首先把所有的可以互相到達的點用並茶几縮一下。
對於傳送門的情況可以 Floyd 跑一遍也可以 bitset
整一遍,還可以對於一個詢問直接搜一下。。
code
#include<bits/stdc++.h> #define int long long #define ull unsigned long long #define f() cout<<"RP++"<<endl using namespace std; inline int read() { int x=0,f=1; char ch=getchar(); while(ch>'9'||ch<'0'){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 N=5e4+10; int n,m,t,q,col,vis[N],fa[N]; int tot=1,head[N],ver[N<<1],nxt[N<<1]; int d1[10]={0,1,-1,0,0}; int d2[10]={0,0,0,1,-1}; vector<int> v[N]; char ch[N]; int id(int x,int y){return (x-1)*m+y;} int find(int x) { if(fa[x]==x) return x; return fa[x]=find(fa[x]); } void add_edge(int x,int y) { ver[++tot]=y; nxt[tot]=head[x]; head[x]=tot; } void dfs(int x) { vis[x]=col; for(int i=head[x];i;i=nxt[i]) if(vis[ver[i]]^col) dfs(ver[i]); } #undef int int main() { #define int long long freopen("treasure.in","r",stdin); freopen("treasure.out","w",stdout); n=read(); m=read(); t=read(); q=read(); for(int i=0;i<=m+1;i++) v[0].push_back('#'),v[n+1].push_back('#'); for(int i=1;i<=n;i++) { scanf("%s",ch+1); v[i].push_back('#'); for(int j=1;j<=m;j++) v[i].push_back(ch[j]); v[i].push_back('#'); } for(int i=1;i<=n*m;i++) fa[i]=i; for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) if(v[i][j]!='#') for(int k=1;k<=4;k++) { int x=i+d1[k],y=j+d2[k]; if(v[x][y]=='#') continue; int p1=id(i,j),p2=id(x,y); if(find(p1)!=find(p2)) fa[find(p1)]=find(p2); } for(int i=1,x,y,x2,y2;i<=t;i++) { x=read(); y=read(); x2=read(); y2=read(); if(find(id(x,y))==find(id(x2,y2))) continue; add_edge(find(id(x,y)),find(id(x2,y2))); } while(q--) { int x,y,x2,y2,p1,p2; x=read(); y=read(); x2=read(); y2=read(); p1=find(id(x,y)); p2=find(id(x2,y2)); col++; dfs(p1); if(vis[p2]==col) printf("1\n"); else printf("0\n"); } return 0; }
T3 序列
解題思路
確實是一個李超線段樹的好題。。。
發現一個位置 p 的最後答案其實就是對於以 p 為右端點向左邊拓展,以 p+1 為左端點可以向右邊拓展的最優解。
這兩種情況類似只討論第一種情況,記字首和陣列是 \(prea,preb\) 。
那麼最優解就是 \(\max\limits_{1\le l\le r}\{(prea_r-prea_{l-1})-k(preb_r-preb_{l-1})\}\) 。
對於一個右端點 p 而言答案就是 \(prea_p-k\times preb_p+\max\limits_{0\le i<p}\{preb_i\times k-prea_i\}\)
直接以 k 為下標,李超線段樹維護就好了。
code
#include<bits/stdc++.h>
#define int long long
#define ull unsigned long long
#define f() cout<<"RP++"<<endl
#define ls x<<1
#define rs x<<1|1
using namespace std;
inline int read()
{
int x=0,f=1; char ch=getchar();
while(ch>'9'||ch<'0'){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 N=1e6+10,INF=1e18;
int n,m,cnt,lim1,lim2=INF,a[N],b[N],lsh[N],prea[N],preb[N],sufa[N],sufb[N],ans[N];
struct node{int p,k;}q[N];
vector< pair<int,int> > pre[N],suf[N];
inline int g(int pos,int k,int b){return lsh[pos]*k+b;}
struct Segment_Tree
{
struct Node{int k,b;}tre[N<<2];
void build(int x,int l,int r)
{
tre[x].b=-INF; tre[x].k=0; if(l==r) return ;
int mid=(l+r)>>1; build(ls,l,mid); build(rs,mid+1,r);
}
void insert(int x,int l,int r,int k,int b)
{
if(g(l,k,b)>=g(l,tre[x].k,tre[x].b)&&g(r,k,b)>=g(r,tre[x].k,tre[x].b)) return tre[x]=(Node){k,b},void();
if(l==r) return ; int mid=(l+r)>>1;
if(g(mid,k,b)>g(mid,tre[x].k,tre[x].b)) swap(tre[x].k,k),swap(tre[x].b,b);
if(g(l,k,b)>g(l,tre[x].k,tre[x].b)) insert(ls,l,mid,k,b);
if(g(r,k,b)>g(r,tre[x].k,tre[x].b)) insert(rs,mid+1,r,k,b);
}
int query(int x,int l,int r,int pos)
{
if(l==r) return g(pos,tre[x].k,tre[x].b);
int mid=(l+r)>>1,temp=g(pos,tre[x].k,tre[x].b);
if(pos<=mid) return max(temp,query(ls,l,mid,pos));
return max(temp,query(rs,mid+1,r,pos));
}
}T1,T2;
#undef int
int main()
{
#define int long long
freopen("seq.in","r",stdin); freopen("seq.out","w",stdout);
n=read(); m=read();
for(int i=1;i<=n;i++) a[i]=read(),b[i]=read(),prea[i]=prea[i-1]+a[i],preb[i]=preb[i-1]+b[i];
for(int i=n;i>=1;i--) sufa[i]=sufa[i+1]+a[i],sufb[i]=sufb[i+1]+b[i];
for(int i=1;i<=m;i++) q[i].p=read(),q[i].k=read(),lsh[i]=q[i].k,lim1=max(lim1,q[i].p),lim2=min(lim2,q[i].p+1);
sort(lsh+1,lsh+m+1); cnt=unique(lsh+1,lsh+m+1)-lsh-1; T1.build(1,1,cnt); T2.build(1,1,cnt);
for(int i=1;i<=m;i++)
{
pre[q[i].p].push_back({i,q[i].k});
if(q[i].p!=n) suf[q[i].p+1].push_back({i,q[i].k});
}
for(int i=0;i<=lim1;i++)
{
for(auto it:pre[i])
{
int temp=lower_bound(lsh+1,lsh+cnt+1,it.second)-lsh;
ans[it.first]+=prea[i]-it.second*preb[i]+T1.query(1,1,cnt,temp);
}
T1.insert(1,1,cnt,preb[i],-prea[i]);
}
for(int i=n+1;i>=lim2;i--)
{
T2.insert(1,1,cnt,sufb[i],-sufa[i]);
for(auto it:suf[i])
{
int temp=lower_bound(lsh+1,lsh+cnt+1,it.second)-lsh;
ans[it.first]+=sufa[i]-it.second*sufb[i]+T2.query(1,1,cnt,temp);
}
}
for(int i=1;i<=m;i++) printf("%lld\n",ans[i]);
return 0;
}
T4 構樹
解題思路
只學會了一個狀壓的做法。。。
首先有一個定理:
Cayley定理:一個完全圖有\(n^{n-2}\)棵無根生成樹,經典問題prufer序列證明
擴充套件Cayley定理:被確定邊分為大小為\(a_1,a_2,\cdots, a_m\)的連通塊,則有\(n^{m-2}\prod {a_i}\)種生成樹
然後我們列舉那些邊是連線的然後根據上面的定理求出來一個至少有若干條邊相同的值,然後二項式反演就好了。
code
#include<bits/stdc++.h>
#define int long long
#define ull unsigned long long
#define f() cout<<"RP++"<<endl
#define count __builtin_popcount
using namespace std;
inline int read()
{
int x=0,f=1; char ch=getchar();
while(ch>'9'||ch<'0'){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 N=8e3+10,mod=1e9+7;
int n,fac[N],ifac[N],g[N],f[N],fa[N],siz[N];
pair<int,int> s[N];
int power(int x,int y,int p=mod)
{
int temp=1; y=(y+mod-1)%(mod-1);
for(;y;y>>=1,x=x*x%p)
if(y&1) temp=temp*x%p;
return temp;
}
int find(int x)
{
if(fa[x]==x) return x;
return fa[x]=find(fa[x]);
}
void add(int &x,int y){x+=y;if(x>=mod)x-=mod;}
int C(int x,int y){if(x<y)return 0;return fac[x]*ifac[y]%mod*ifac[x-y]%mod;}
#undef int
int main()
{
#define int long long
freopen("tree.in","r",stdin); freopen("tree.out","w",stdout);
n=read(); fac[0]=ifac[0]=1;
for(int i=1,x,y;i<n;i++) s[i].first=read(),s[i].second=read();
for(int i=1;i<=n;i++) fac[i]=fac[i-1]*i%mod; ifac[n]=power(fac[n],mod-2);
for(int i=n-1;i>=1;i--) ifac[i]=ifac[i+1]*(i+1)%mod;
for(int sta=0;sta<(1ll<<n-1);sta++)
{
int temp=1;
for(int i=1;i<=n;i++) fa[i]=i,siz[i]=1;
for(int i=1;i<n;i++)
if(((sta>>i-1)&1)&&find(s[i].first)!=find(s[i].second))
{
siz[find(s[i].second)]+=siz[find(s[i].first)];
fa[find(s[i].first)]=find(s[i].second);
}
for(int i=1;i<=n;i++) if(find(i)==i) temp=temp*siz[i]%mod;
add(g[count(sta)],temp);
}
for(int i=0;i<n;i++) g[i]=g[i]*power(n,n-i-2)%mod;
for(int i=0;i<n;i++)
{
for(int j=i,bas=1;j<n;j++,bas=-bas)
add(f[i],(bas+mod)%mod*C(j,i)%mod*g[j]%mod);
}
for(int i=0;i<n;i++) printf("%lld ",f[i]);
return 0;
}