NOIP提高组模拟赛26
又双叒叕炸了。
A.LCIS
蓝书原题,但是我没有蓝书..
没关系反正水过了
复杂度极其不正确
好在老殷复杂度也不对所以没有上交数据
\(upd:\)好了,看懂了,懒得打了,粘一下
考场打的非常诡异,头一回觉得\(vector\)这么好用,大爱vector
\(f[i][j]\)表示\(a\)前\(i\)个\(b\)前\(j\)个的\(LCIS\)
为了优化,我们只使用\(a[i]==b[j]\)的\(i\)和\(j\)
利用\(vector\)开桶,快速找到所有与\(a[i]\)相等的\(b[j]\)的下标
转移\(f[i][j]=max(f[p][q]+1)\)
要求\(a[p]<a[i]\),并且\(f[p][q]>0\)
过程中不断更新\(ans\)
就是这样,复杂度假的一批,但是过了
code
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
typedef long long ll;
const int maxn=3005;
int n,cnt;
ll a[maxn],b[maxn],lsh[maxn<<1|1];
int f[maxn][maxn];
vector<int>v[maxn<<1|1];
int rem[maxn],tot;
int main(){
scanf("%d",&n);
for(int i=1;i<=n;++i)scanf("%lld",&a[i]);
for(int i=1;i<=n;++i)scanf("%lld",&b[i]);
for(int i=1;i<=n;++i)lsh[++cnt]=a[i];
for(int i=1;i<=n;++i)lsh[++cnt]=b[i];
sort(lsh+1,lsh+cnt+1);cnt=unique(lsh+1,lsh+cnt+1)-lsh-1;
for(int i=1;i<=n;++i)b[i]=lower_bound(lsh+1,lsh+cnt+1,b[i])-lsh;
for(int i=1;i<=n;++i)a[i]=lower_bound(lsh+1,lsh+cnt+1,a[i])-lsh;
for(int i=1;i<=n;++i)v[b[i]].push_back(i);
int ans=0;
for(int i=1;i<=n;++i){
int s=v[a[i]].size();
if(s==0)continue;
for(int j=0;j<s;++j)f[i][v[a[i]][j]]=1;
ans=max(ans,1);
for(int j=1;j<=tot;++j){
if(a[rem[j]]>=a[i])continue;
int ss=v[a[rem[j]]].size();
for(int p=0;p<s;++p){
for(int q=0;q<ss;++q){
if(v[a[i]][p]<v[a[rem[j]]][q])break;
f[i][v[a[i]][p]]=max(f[i][v[a[i]][p]],f[rem[j]][v[a[rem[j]]][q]]+1);
}
ans=max(ans,f[i][v[a[i]][p]]);
}
}
rem[++tot]=i;
}
printf("%d\n",ans);
return 0;
}
B. 物流运输
一看这题:这不费用瘤板子题吗?
然后,就没有然后了
最后\(30min\)想到装压解法,开始码,在最后\(3min\)交上了
但是\(MLE\)了,,,,,,我发现边权写的是\(>0\),猜测会炸\(int\),但是没有。。开了\(long long\)的下场是\(30->0\)
对,没错,装压不对
解法是跑多次最短路\(DP\)
枚举上一次什么时候换的航线
每次最短路求的时候屏蔽\(j-i\)天不能走的点
这样其实只需要\(n^2\)次最短路
比装压强多了
code
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#include<iostream>
using namespace std;
int read(){
char c;int x=0;c=getchar();
while(c<'0'||c>'9')c=getchar();
while(c>='0'&&c<='9')x=x*10+c-'0',c=getchar();
return x;
}
typedef long long ll;
const int maxn=23;
const int maxm=805;
int n,m,K,E,tot=1,head[23],ans[106];
bool mp[103][23];
struct edge{int to,net;int w;}e[maxm];
void add(int u,int v,int w){
e[++tot].net=head[u];
head[u]=tot;
e[tot].to=v;
e[tot].w=w;
}
bool vis[maxn];
int dis[maxn];
queue<int>q;
int spfa(int l,int r){
memset(vis,0,sizeof(vis));
for(int i=l;i<=r;++i)
for(int j=2;j<m;++j)
if(mp[i][j])vis[j]=1;
memset(dis,0x3f,sizeof(dis));
dis[1]=0;q.push(1);
while(!q.empty()){
int x=q.front();q.pop();vis[x]=0;
for(int i=head[x];i;i=e[i].net){
int v=e[i].to;
if(dis[v]>dis[x]+e[i].w){
dis[v]=dis[x]+e[i].w;
if(!vis[v])q.push(v);
}
}
}
return dis[m];
}
int main(){
n=read();m=read();K=read();E=read();
for(int i=1;i<=E;++i){
int u,v,w;
u=read();v=read();w=read();
add(u,v,w);add(v,u,w);
}
int d;scanf("%d",&d);
for(int i=1;i<=d;++i){
int P,A,B;
P=read();A=read();B=read();
for(int j=A;j<=B;++j)mp[j][P]=1;
}
memset(ans,0x3f,sizeof(ans));
for(int i=1;i<=n;++i){
int k=spfa(1,i);
if(k!=dis[m+1])ans[i]=k*i;
for(int j=1;j<i;++j){
int k=spfa(j+1,i);
if(k!=dis[m+1])ans[i]=min(ans[i],ans[j]+K+k*(i-j));
}
}
printf("%d\n",ans[n]);
return 0;
}
C. tree
奇妙的题,\(Eafoo\)太强了
考场试图随机化,但是一分都没有随到。。
正解注意到边权范围很小,考虑给白边加上一个\(dt\)
\(-100<=dt<=100\)
显然\(dt\)越小,白边越容易被使用,反之亦然
那么这个\(dt\)就可以二分了
但是会有\(dt=i\)时\(sumwhileedge>need\)而\(dt=i+1\)时\(sumwhileedge<need\)
这个时候,直接取一侧,\(sum-need*dt\)即可,取哪一侧好像取决于排序时权相等时白边在前还是黑边在前
code
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int maxn=50005;
const int maxm=100005;
int V,E,need;
struct edge{int u,v,w,c;}e[maxm];
bool cmp(edge x,edge y){return x.w==y.w?x.c<y.c:x.w<y.w;}
struct SET{
int f[maxn];
void pre(int x){for(int i=1;i<=x;++i)f[i]=i;}
int fa(int x){return f[x]=f[x]==x?x:fa(f[x]);}
bool hb(int x,int y){x=fa(x);y=fa(y);if(x==y)return false;f[x]=y;return true;}
}S;
int sb,sum;
void work(int dt){
for(int i=1;i<=E;++i)if(e[i].c==0)e[i].w+=dt;
sort(e+1,e+E+1,cmp);
sb=sum=0;int block=V;S.pre(V);
for(int i=1;i<=E;++i){
if(S.hb(e[i].u,e[i].v)){
sum+=e[i].w;
if(e[i].c==0)++sb;
--block;if(block==1)break;
}
}
sum=sum-dt*need;
for(int i=1;i<=E;++i)if(e[i].c==0)e[i].w-=dt;
}
bool check(int dt){
work(dt);
if(sb>=need)return true;
return false;
}
int main(){
scanf("%d%d%d",&V,&E,&need);
for(int i=1;i<=E;++i)scanf("%d%d%d%d",&e[i].u,&e[i].v,&e[i].w,&e[i].c);
for(int i=1;i<=E;++i)++e[i].u,++e[i].v;
int ans=0x3f,l=-100,r=100;
while(l<r){
if(r-l<=5){
for(int i=r;i>=l;--i)if(check(i)){ans=sum;break;}break;
}
int mid=(l+r)/2;
if(check(mid))l=mid;
else r=mid-1;
}
printf("%d\n",ans);
return 0;
}
D. 建造游乐园
在学生的联名提议下,HZ决定在校园内建造一个新型游乐场。
HZ什么时候这么好了?哦做题啊,那没事了
有点结论题的意思
首先不难发现答案其实是\(n\)个点的联通欧拉图的个数\(\times n*(n-1)/2\)
问题来了\(n\)个点的联通欧拉图的个数怎么求?
不会了
设\(g[i]\)表示\(n\)个点构成的所有点的度为偶数的图的数量
有\(g[i]=2^{C_{i-1}^2}\)
解释一下,在\(i-1\)个点构成的所有图中,只要将其中所有度数为奇数的点(一定为偶数个)与\(i\)连边就是一个所有点的度为偶数的图,所以就是在\(i-1\)个点中共\(C_{i-1}^2\)条边,每条边可有可无
这样我们需要去掉不连通的情况
先放柿子
\(\displaystyle f[i]=g[i]-\sum_{j=1}^{i-1}f[j]\times g[i-j]\times C_{i-1}^{j-1}\)
解释一下,我们钦定一个点,他在一个大小为\(j\)的联通快内,剩余部分是一个\(i-j\)大小的图,与他在同一个联通块内的点的集合有\(C_{i-1}^{j-1}\)种选法
然后注意用作指数的取模应该模\(\phi p\),(扩展欧拉定理)
但是由于本题的指数位置都很小,所以没有特别处理
code
#include<cstdio>
#include<cstring>
using namespace std;
typedef long long ll;
const int mod=1000000007;
const int maxn=2005;
int n,fac[maxn],inv[maxn],g[maxn],f[maxn];
int qpow(int x,int y){
int ans=1;
for(;y;y>>=1,x=1ll*x*x%mod)if(y&1)ans=1ll*ans*x%mod;
return ans%mod;
}
int get_c(int n,int m){return 1ll*fac[n]*inv[n-m]%mod*inv[m]%mod;}
int main(){
scanf("%d",&n);
fac[0]=1;for(int i=1;i<=n;++i)fac[i]=fac[i-1]*1ll*i%mod;
inv[n]=qpow(fac[n],mod-2);inv[0]=1;
for(int i=n-1;i>=1;--i)inv[i]=inv[i+1]*1ll*(i+1)%mod;
for(int i=1;i<=n;++i)g[i]=qpow(2,get_c(i-1,2));
for(int i=1;i<=n;++i){
f[i]=g[i];for(int j=1;j<i;++j)f[i]=(f[i]-1ll*f[j]*g[i-j]%mod*get_c(i-1,j-1)%mod+mod)%mod;
}
printf("%d\n",(int)(f[n]*1ll*get_c(n,2)%mod));
return 0;
}