NOIP模擬84(多校17)
T1 寶藏
解題思路
考場上一眼出 \(nlog^2\) 做法,然後沒看見是 1s 3e5
的資料,我竟然以為自己切了??
考完之後嘗試著把二分改為指標的移動,然後就過了??或許是資料水吧,感覺自己的做法指標好像並不滿足單調性。。
口胡一下正解,做法差不多,只不過列舉的方式改變了,但是都需要先對於 w 進行排序,列舉每一種長度的序列,單調指標維護最大的合法的值。
這個是有單調性的,然後主席樹或者權值線段樹維護均可。
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=3e5+10,M=1e6+10; int n,m,q,lim,ans[N]; struct Node{int w,t;}s[N]; struct Segment_Tree { int root,all; struct node{int siz,dat;}tre[M<<2]; #define push_up(x) tre[x].dat=tre[ls].dat+tre[rs].dat,tre[x].siz=tre[ls].siz+tre[rs].siz void insert(int x,int l,int r,int pos,int val) { if(l==r) return tre[x].siz+=val,tre[x].dat+=val*pos,void(); int mid=(l+r)>>1; if(pos<=mid) insert(ls,l,mid,pos,val); else insert(rs,mid+1,r,pos,val); push_up(x); } int query(int x,int l,int r,int k) { if(!tre[x].siz||!k) return 0; if(l==r) return tre[x].dat*k/tre[x].siz; int mid=(l+r)>>1; if(tre[ls].siz>k) return query(ls,l,mid,k); return tre[ls].dat+query(rs,mid+1,r,k-tre[ls].siz); } }T1,T2; bool comp(Node x,Node y){return x.w<y.w;}; #undef int int main() { #define int long long freopen("treasure.in","r",stdin); freopen("treasure.out","w",stdout); n=read(); m=read(); q=read(); memset(ans,-1,sizeof(ans)); for(int i=1;i<=n;i++) s[i].w=read(),s[i].t=read(),lim=max(lim,s[i].t); sort(s+1,s+n+1,comp); for(int i=1;i<=n;i++) T2.insert(1,0,lim,s[i].t,1); for(int i=1,val=0;i<=n;i++) { T2.insert(1,0,lim,s[i].t,-1); val=min(val,min(i-1,n-i)); while(val<min(i-1,n-i)&&T1.query(1,1,lim,val+1)+T2.query(1,1,lim,val+1)+s[i].t<=m) val++; while(val>0&&T1.query(1,1,lim,val)+T2.query(1,1,lim,val)+s[i].t>m) val--; ans[val]=max(ans[val],s[i].w); T1.insert(1,0,lim,s[i].t,1); } for(int i=n/2;i>=0;i--) ans[i]=max(ans[i],ans[i+1]); while(q--){int x;x=read();printf("%lld\n",ans[min(n,x/2)]);} return 0; }
T2 尋找道路
解題思路
首先考慮去除前導 0 的影響,直接搜尋一遍查詢所有到 1 節點距離為 0 的點記錄下來就好了。
剩下的部分就是字典序以及長度的問題了,那麼長度的問題直接 BFS 就可以了。
字典序大小的話,對於佇列中長度一致並且數字序列相同的一起拿出來,然後優先掃 0 邊權的邊再掃 1 邊權的邊。
標記一下,保證每個節點只訪問一次。
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=1e6+10,mod=1e9+7,INF=1e18; int n,m,top,sta[N],len[N],dis[N]; int tot=1,head[N],ver[N<<1],nxt[N<<1],edge[N<<1]; vector<int> dis0; queue<int> q; bool vis[N]; void add_edge(int x,int y,int val) { ver[++tot]=y; edge[tot]=val; nxt[tot]=head[x]; head[x]=tot; } #undef int int main() { #define int long long freopen("path.in","r",stdin); freopen("path.out","w",stdout); n=read(); m=read(); memset(len,0x3f,sizeof(len)); dis[1]=len[1]=0; for(int i=1,x,y,z;i<=m;i++) x=read(),y=read(),z=read(),add_edge(x,y,z); q.push(1); dis0.push_back(1); while(!q.empty()) { int x=q.front(); q.pop(); for(int i=head[x];i;i=nxt[i]) if(!vis[ver[i]]&&!edge[i]) vis[ver[i]]=true,dis0.push_back(ver[i]),q.push(ver[i]); } for(auto it:dis0) q.push(it),dis[it]=len[it]=0; while(!q.empty()) { top=0; sta[++top]=q.front(); q.pop(); while(!q.empty()&&len[q.front()]==len[sta[1]]&&dis[q.front()]==dis[sta[1]]) sta[++top]=q.front(),q.pop(); for(int i=1;i<=top;i++) { int x=sta[i]; for(int j=head[x];j;j=nxt[j]) { int to=ver[j],val=edge[j]; if(val||vis[to]||len[to]<=len[x]+1) continue; len[to]=len[x]+1; dis[to]=dis[x]*2%mod; vis[to]=true; q.push(to); } } for(int i=1;i<=top;i++) { int x=sta[i]; for(int j=head[x];j;j=nxt[j]) { int to=ver[j],val=edge[j]; if(!val||vis[to]||len[to]<=len[x]+1) continue; len[to]=len[x]+1; dis[to]=(dis[x]*2+1)%mod; vis[to]=true; q.push(to); } } } for(int i=2;i<=n;i++) printf("%lld ",len[i]>=INF?-1ll:dis[i]); return 0; }
T3 豬國殺
解題思路
其實是個假期望,計數 DP 。
我們只需要知道每一種方案的總和了,最後乘上一個 \(A^n\) 。
設 \(g_{i,j,k}\) 表示有 多少個⻓度為 \(i\) 的正整數序列滿足每一個數字不大於 \(j\) 且所有數字總和不超過 \(k\) 。
假設我們能夠求出來這個值,考慮如何計算答案。
列舉選的牌中的最大值 \(j\) ,最大值個數 \(k\) ,以及選了 \(i\) 個小於 \(j\) 的牌,於是就有了:
\[\sum\limits_{i=0}^n\sum\limits_{j=1}^A\sum\limits_{k=1}^{n-i}g_{i,j-1,m-j\times k}\times \binom{n}{i}\sum\limits_{t=k}^{n-i}\binom{n-i}{t}\times(A-j)^{n-i-t} \]對於計算過方案數的兩個序列就可以視為序列中的元素是等價的了,也就是再乘上一個可重集排列。
因為我們要選擇 \(k\) 個 \(j\) 但是序列中不一定只有 \(k\) 個 \(j\) 因此我們需要讓前面的牌的總和是 \(m-j\times k\) 然後列舉後面有多少個 \(j\) 同時計算剩下的取值的個數。
對於 \(g_{i,j,k}\) 可以通過列舉多少個大於 \(j\) 的數字進行計算,可以運用擋板法,由於擋板之間 1 的個數可能會超過 \(j\) 因此需要容斥一下:
\[g_{i,j,k}=\sum\limits_{t=0}^i(-1)^t\binom{i}{t}\binom{k-t\times j}{i} \]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=110,M=1010,mod=998244353;
int n,m,lim,mx,ans,fac[M],ifac[M];
void add(int &x,int y){x+=y;if(x>=mod)x-=mod;}
int power(int x,int y,int p=mod)
{
int temp=1;
while(y)
{
if(y&1) temp=temp*x%p;
x=x*x%p; y>>=1;
}
return temp;
}
int C(int x,int y){return x<y?0:fac[x]*ifac[y]%mod*ifac[x-y]%mod;}
int g(int i,int j,int k)
{
int sum=0;
for(int p=0,sym=1;p<=i;p++,sym=-sym)
{
int temp=C(i,p)*C(k-p*j,i)%mod;
if(temp) add(sum,sym*temp+((~sym)?0:mod));
else break;
}
return sum;
}
#undef int
int main()
{
#define int long long
freopen("legend.in","r",stdin); freopen("legend.out","w",stdout);
n=read(); m=read(); lim=read(); mx=max(n,max(m,lim));
fac[0]=ifac[0]=1; for(int i=1;i<=mx;i++) fac[i]=fac[i-1]*i%mod;
ifac[mx]=power(fac[mx],mod-2); for(int i=mx-1;i>=1;i--) ifac[i]=ifac[i+1]*(i+1)%mod;
for(int i=0;i<=n;i++)
for(int j=1;j<=lim;j++)
for(int k=1;k<=n-i;k++)
{
int base=0,temp=C(n,i)%mod*g(i,j-1,m-j*k)%mod;
if(!temp) continue;
for(int p=k;p<=n-i;p++)
{
int temp=C(n-i,p); if(!temp) break;
add(base,temp*power(lim-j,n-i-p)%mod);
}
if(base) add(ans,base*temp%mod);
}
printf("%lld",ans*power(power(lim,n),mod-2)%mod); return 0;
}
T4 數樹
解題思路
列舉以哪個節點為根以及子樹和 T2 的匹配程度來判斷。
具體實現可以 Hash+素數 防止重複。
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=3e3+10,M=20,bas=23,mod=998244353;
int n,m,cnt,ans,pri[N],siz[N];
int tot=1,head[N],ver[N<<1],nxt[N<<1];
ull has[N];
unordered_map<ull,int> ys,f[N];
unordered_map<ull,bool> can,mp;
vector<int> v[M];
bitset<N> vis;
void add_edge(int x,int y)
{
ver[++tot]=y;
nxt[tot]=head[x];
head[x]=tot;
}
void Get_Prime()
{
for(int i=2;i<=1000;i++)
{
if(!vis[i]) pri[++cnt]=i;
for(int j=1;j<=cnt&&pri[j]*i<=1000;j++)
{
vis[i*pri[j]]=true;
if(i%pri[j]==0) break;
}
}
}
void pre_dfs(int x,int fa)
{
siz[x]=has[x]=1;
for(auto to:v[x])
{
if(to==fa) continue;
pre_dfs(to,x); siz[x]+=siz[to];
has[x]+=has[to]*pri[siz[to]+bas];
}
mp.insert(make_pair(has[x],true));
ys.insert(make_pair(has[x],siz[x]+bas));
}
void dfs(int x,int fa)
{
f[x].insert(make_pair(1,1));
for(int i=head[x];i;i=nxt[i])
{
int to=ver[i]; if(to==fa) continue;
unordered_map<ull,int> temp=f[x]; dfs(to,x);
for(auto it1:temp) for(auto it2:f[to])
{
ull rec=it1.first+it2.first*pri[ys.find(it2.first)->second];
f[x][rec]=(f[x][rec]+it1.second*it2.second)%mod;
}
}
vector<ull> dela;
for(auto it:f[x]) if(mp.find(it.first)==mp.end()) dela.push_back(it.first);
for(auto it:dela) f[x].erase(it);
}
#undef int
int main()
{
#define int long long
freopen("count.in","r",stdin); freopen("count.out","w",stdout);
n=read(); Get_Prime();
for(int i=1,x,y;i<n;i++)
x=read(),y=read(),
add_edge(x,y),add_edge(y,x);
m=read();
for(int i=1,x,y;i<m;i++)
x=read(),y=read(),
v[x].push_back(y),v[y].push_back(x);
for(int i=1;i<=m;i++) pre_dfs(i,0),can.insert(make_pair(has[i],true));
dfs(1,0);
for(auto it:can)
for(int i=1;i<=n;i++)
if(f[i].find(it.first)!=f[i].end())
ans=(ans+f[i].find(it.first)->second)%mod;
printf("%lld",ans);
return 0;
}