【自家测试】2018-3-3
A 题
Dave 写一个程序,他希望这个程序运行之后可以输出长度为 n 的数组 a。
在这个程序中 Dave 一开始会开若干个临时变量,并在一开始的时候对某一个进行赋值,然
后将其输出。
接下来,Dave 每次可以选 3 个变量(变量可以是相同的),设变量为 bi,bj,bk,然后进行
如下操作
1,bi = bj + bk
2,输出 bi
由于 Dave 电脑内存极小,现在他想他至少需要多少个临时变量。
输入
第一行一个 n 表示数组长度
第二行 n 个数,表示需要输出的数组
输出
一个数,表示变量的个数, 无解输出-1
输入样例
5
1 2 3 6 8
输出样例
2
样例解释
b1 := 1;
b2 := b1+b1;
b1 := b1+b2;
b1 := b1+b1;
b1 := b1+b2.
数据范围
对于 40%的数据 0<a[i]<=1000
对于 100%的数据 0<a[i]<10^9 ,n<=23
奥妙重重的状压dp。
奥妙之处在于它是一个大概4e7的暴力,一个从1递增的数据就可以卡成3s多,加了一个玄学剪枝可以1s过,结果wa完了。。然后不加剪枝竟然过了??
奥妙重重
//Achen
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<vector>
#include<cstdio>
#include<queue>
#include<cmath>
const int N=24;
typedef long long LL;
using namespace std;
int n,a[N],isok[N],ck,dp[2][1<<23],vis[1<<23],lastuse[N],inf,ans;
template<typename T>void read(T &x) {
char ch=getchar(); x=0; T f=1;
while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
if(ch=='-') f=-1,ch=getchar();
for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0'; x*=f;
}
int ecnt,fir[N],nxt[N*N],to[N*N];
void add(int u,int v) {
nxt[++ecnt]=fir[u]; fir[u]=ecnt; to[ecnt]=v;
}
#define DEBUG
int main() {
#ifdef DEBUG
freopen("A.in","r",stdin);
freopen("A.out","w",stdout);
#endif
read(n);
for(int i=1;i<=n;i++) read(a[i]);
for(int i=2;i<=n;i++) {
for(int j=1;j<i;j++)
for(int k=j;k<i;k++)
if(a[j]+a[k]==a[i]) {
add(i,((1<<j-1)|(1<<k-1)));
isok[i]=1;
}
ck+=isok[i];
}
if(ck<n-1) { puts("-1"); return 0; }
int o=0;
memset(dp,127,sizeof(dp));
inf=dp[0][0]; ans=inf;
dp[o][1]=1;
for(int i=2;i<=n;i++) {
o^=1;
for(int jj=0,j;jj<(1<<i-2);jj++) {
j=(jj|(1<<i-2));
if(dp[o^1][j]!=inf) {
for(int k=fir[i];k;k=nxt[k]) if((j&to[k])==to[k]) {
vis[j|(1<<i-1)]=i;
dp[o][j|(1<<i-1)]=min(dp[o][j|(1<<i-1)],dp[o^1][j]+1);
for(int l=j;l;l-=(l&(-l))) {
int lb=(l&(-l));
vis[(j^lb)|(1<<i-1)]=i;
dp[o][(j^lb)|(1<<i-1)]=min(dp[o][(j^lb)|(1<<i-1)],dp[o^1][j]);
}
}
}
}
for(int j=1;j<(1<<i);j++) {
if(vis[j]!=i) dp[o][j]=inf;
else if(i==n) ans=min(ans,dp[o][j]);
/*if(vis[j]==i) {
int cnt=0;
for(int l=j;l;l-=(l&(-l))) cnt++;
if(cnt>(n-i)*2+1) dp[o][j]=inf;
}*/
}
}
printf("%d\n",ans);
return 0;
}
/*
5
1 2 3 6 8
23
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
23
1 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3
*/
B 题
有一棵 n 个节点的树,树上的节点从 1 到 n 标号。我们定义一个区间[l,r]的长度为 r-l+1。 对于该树的一棵子树(一个连通子图),我们定义其价值为一个最长的区间[l,r]的长度,并且 所有标号为 l,l+1,...,r 的节点均属于这棵子树。
现在你需要考虑所有大小不超过 k 的子树,找出一个价值最大的。
注意在这个问题里面给出 的树是没有根的。
输入:
第一行包含两个整数 n,k。
接下来的 n-1 行每行包含两个整数 ai,bi,表示在 ai 与 bi 间有一条边。
我们保证这个图构成一棵树。
输出:
输出一个整数占一行,表示最大的价值。
样例输入:
10 6
4 10 10
6 2 9
9 6 8
5 7 1
4 7 7
3 1 8
样例输出:
3
数据范围:
对于 10%的数据,1 <= k <= n <= 20;
对于 30%的数据,1 <= k <= n <= 1000;
另外有 10%的数据,这棵树是一条链。
对于 100%的数据,1 <= k <= n <= 100000。
用两个指针l,r维护l~r形成一个联通块的情况,,开一个set按dfs序维护l~r之间的点,那么目前的lca就是dfs序最小的点和最大点的lca。
r从1到n扫,加点,当联通块的sz>k时l++,删点。
加点时加入新点算一遍现在的lca,和从前的lca比较,若有变换,sz加上上一个lca到这个lca之间的点即可。
若无变化,用树状数组维护子树信息,查询一下当前点的儿子中有没有仍然没被删掉的点,若有则sz不变,否则从set中找到和当前点相邻的点求lca,取深度大的,sz减掉当前点到这个lca之间的点即可。
删点和加点的方式类似。
//Achen
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<vector>
#include<cstdio>
#include<queue>
#include<cmath>
#include<set>
const int N=100007;
typedef long long LL;
using namespace std;
int n,k,ans=1,szz,lca;
template<typename T>void read(T &x) {
char ch=getchar(); x=0; T f=1;
while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
if(ch=='-') f=-1,ch=getchar();
for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0'; x*=f;
}
int ecnt,fir[N],nxt[N<<1],to[N<<1],f[N][17],R[N];
void add(int u,int v) {
nxt[++ecnt]=fir[u]; fir[u]=ecnt; to[ecnt]=v;
nxt[++ecnt]=fir[v]; fir[v]=ecnt; to[ecnt]=u;
}
int sum[N];
void ADD(int x,int v) { for(int i=x;i<=n;i+=(i&(-i))) sum[i]+=v; }
int qry(int x) { int rs=0; for(int i=x;i;i-=(i&(-i))) rs+=sum[i]; return rs; }
int get_lca(int x,int y) {
if(R[x]<R[y]) swap(x,y);
for(int i=16;i>=0;i--)
if(R[f[x][i]]>=R[y])
x=f[x][i];
if(x==y) return x;
for(int i=16;i>=0;i--)
if(f[x][i]!=f[y][i])
x=f[x][i],y=f[y][i];
return f[x][0];
}
int dfs_clock,dfn[N],sz[N];
void dfs(int x,int fa) {
dfn[x]=++dfs_clock;
f[dfn[x]][0]=dfn[fa];
R[dfn[x]]=R[dfn[fa]]+1;
sz[dfn[x]]=1;
for(int i=1;i<=16;i++) f[dfn[x]][i]=f[f[dfn[x]][i-1]][i-1];
for(int i=fir[x];i;i=nxt[i]) if(to[i]!=fa) {
dfs(to[i],x);
sz[dfn[x]]+=sz[dfn[to[i]]];
}
}
int inque(int x) { return qry(x+sz[x]-1)-qry(x); }
set<int>s;
void ins(int x) {
s.insert(x);
if(s.size()==1) { ADD(x,1); lca=x; szz=1; return; }
int nlca=get_lca(*s.begin(),*s.rbegin());
if(nlca==lca) {
ADD(x,1);
if(!inque(x)) {
int y=(s.lower_bound(x)==s.begin())?0:(*--s.lower_bound(x));
int z=(++s.lower_bound(x)==s.end())?0:(*++s.lower_bound(x));
if(!(y*z)) y=z=(y^z);
y=get_lca(x,y);
z=get_lca(x,z);
if(R[z]>R[y]) swap(z,y);
szz+=R[x]-R[y];
}
}
else {
ADD(x,1);
szz+=R[x]-R[nlca]+R[lca]-R[nlca];
}
lca=nlca;
}
void del(int x) {
if(s.size()==1) { s.erase(x); ADD(x,-1); szz=0; return;}
s.erase(x);
int nlca=get_lca(*s.begin(),*s.rbegin());
s.insert(x);
if(nlca==lca) {
if(inque(x)<=1) {
int y=(s.lower_bound(x)==s.begin())?0:(*--s.lower_bound(x));
int z=(++s.lower_bound(x)==s.end())?0:(*++s.lower_bound(x));
if(!(y*z)) y=z=(y^z);
y=get_lca(x,y);
z=get_lca(x,z);
if(R[z]>R[y]) swap(z,y);
szz-=R[x]-R[y];
}
ADD(x,-1);
}
else {
ADD(x,-1);
szz-=R[x]-R[lca]+R[nlca]-R[lca];
}
s.erase(x);
lca=nlca;
}
void solve() {
int l=1;
szz=0; lca=0;
for(int r=1;r<=n;r++) {
ins(dfn[r]);
while(szz>k)
del(dfn[l++]);
ans=max(ans,r-l+1);
}
}
#define DEBUG
int main() {
#ifdef DEBUG
freopen("B.in","r",stdin);
freopen("B.out","w",stdout);
#endif
read(n); read(k);
for(int i=1;i<n;i++) {
int u,v;
read(u); read(v);
add(u,v);
}
dfs(1,0);
solve();
printf("%d\n",ans);
return 0;
}
C 题
森林里共有 n 个结点,结点从 1 到 n 编号。森林里共有 m 条有向边,一些结点之间通过有
向边连接。通过有向边可以从一个结点走到另一个结点,但是通过一条边需要花费其所对应
的时间。
有一天,一名农夫闯入了森林。他观察发现,每天都会有一只兔子从 1 号结点跑到 n
号结点。兔子都很聪明,它总会选择一条从 1 到 n 花费总时间最少的路径,但是兔子每天可
能会选择不同的路径。农夫仔细研究之后发现无论兔子选择哪条最短路径,都必须经过一些
特定的结点。也就是说,任何一条从 1 到 n 的最短路都要经过这些结点。于是他只要选择一
个这样的结点,在那里“守株待兔”便可以抓到兔子了。现在他想知道这样的结点一共有多
少个。
输入描述
第一行两个数 n,m 表示木桩数和边数。
接下来有 m 行,每行 3 个数 u v w,表示从 u 号木桩到 v 号木桩有一条耗时为 w 的有向边。
输入保证没有自环和重边。
输出描述
如果不能从 1 号木桩到 n 号木桩输出-1,否则输出特定木桩个数。
输入样例
5 5
1 2 2
1 3 5
2 4 1
3 4 4
4 5 10
输出样例
4
数据范围
20%的数据 n<=1000,m<=2000
100%的数据 n<=100000,m<=100000
1=<u,v<=n,1=<w<=1000
一种方法是求出起点到每个点的最短路和最短路条数,终点同样,算出经过最短路上的每个点的最短路条数,若是等于总数则计算答案。
因为最短路会很多,需要hash。
另一种方法是求出最短路后建出新图求支配点。
一个过了80的求割点算法。暂时不知道为何会WA。
//Achen
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<vector>
#include<cstdio>
#include<queue>
#include<cmath>
const int N=100007;
typedef long long LL;
using namespace std;
int n,m;
template<typename T>void read(T &x) {
char ch=getchar(); x=0; T f=1;
while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
if(ch=='-') f=-1,ch=getchar();
for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0'; x*=f;
}
int ecnt,fir[N],nxt[N],to[N],val[N];
int ec,fi[N],nx[N],tt[N],va[N];
void add(int u,int v,int w) {
nxt[++ecnt]=fir[u]; fir[u]=ecnt; to[ecnt]=v; val[ecnt]=w;
nx[++ec]=fi[v]; fi[v]=ec; tt[ec]=u; va[ec]=w;
}
deque<int>que;
int dis[N],dis2[N],vis[N],is[N];
void spfa(int fir[],int nxt[],int to[],int val[],int dis[]) {
while(!que.empty()) {
int x=que.front();
que.pop_front();
vis[x]=0;
for(int i=fir[x];i;i=nxt[i]) {
if(dis[to[i]]>dis[x]+val[i]) {
dis[to[i]]=dis[x]+val[i];
if(!vis[to[i]]) {
vis[to[i]]=1;
if(!que.empty()) {
if(dis[to[i]]<dis[que.front()]) que.push_front(to[i]);
else que.push_back(to[i]);
}
else que.push_back(to[i]);
}
}
}
}
}
int cnt,Fir[N],Nxt[N<<1],To[N<<1];
void ADD(int u,int v) {
Nxt[++cnt]=Fir[u]; Fir[u]=cnt; To[cnt]=v;
Nxt[++cnt]=Fir[v]; Fir[v]=cnt; To[cnt]=u;
}
int dfs_clock,dfn[N],low[N],cut[N],top,rt,rc;
void tarjan(int x,int rt) {
low[x]=dfn[x]=++dfs_clock;
for(int i=Fir[x];i;i=Nxt[i]) {
if(!dfn[To[i]]) {
if(x==rt) rc++;
tarjan(To[i],rt);
low[x]=min(low[x],low[To[i]]);
if(x!=rt&&low[To[i]]>=dfn[x]) cut[x]=1;
}
else low[x]=min(low[x],dfn[To[i]]);
}
if(x==rt&&rc>1) cut[x]=1;
}
#define DEBUG
int main() {
#ifdef DEBUG
freopen("C.in","r",stdin);
freopen("C.out","w",stdout);
#endif
read(n); read(m);
for(int i=1;i<=m;i++) {
int u,v,w;
read(u); read(v); read(w);
add(u,v,w);
}
memset(dis,127,sizeof(dis));
memset(dis2,127,sizeof(dis2));
dis[1]=0; que.push_back(1);
spfa(fir,nxt,to,val,dis);
dis2[n]=0; que.push_back(n);
spfa(fi,nx,tt,va,dis2);
for(int i=1;i<=n;i++)
if(dis[i]+dis2[i]==dis[n]) is[i]=1;
for(int i=1;i<=n;i++) if(is[i]) {
for(int j=fir[i];j;j=nxt[j]) if(is[to[j]]) {
ADD(i,to[j]);
}
}
for(int i=1;i<=n;i++) if(is[i]) {
tarjan(i,i); break;
} cut[1]=cut[n]=1;
int ans=0;
for(int i=1;i<=n;i++) ans+=cut[i];
printf("%d\n",ans);
return 0;
}