目录
题目地址:https://codeforces.com/contest/1340
A Nastya and Strange Generator
题意:
思路:
代码:
#include <bits/stdc++.h>
#define mem(a,b) memset(a,b,sizeof(a))
#define REP(i,a,b) for(int i=(a);i<=(int)(b);i++)
#define REP_(i,a,b) for(int i=(a);i>=(b);i--)
using namespace std;
typedef long long LL;
typedef vector<int> VI;
int read()
{
int x=0,flag=1;
char c=getchar();
while((c>'9' || c<'0') && c!='-') c=getchar();
if(c=='-') flag=0,c=getchar();
while(c<='9' && c>='0') {x=(x<<3)+(x<<1)+c-'0';c=getchar();}
return flag?x:-x;
}
const int maxn=1e5+5;
int a[maxn],n;
bool can(int l,int r)
{
REP(i,l+1,r-1) if(a[i]!=a[i-1]+1) return 0;
return 1;
}
int main()
{
//freopen("input.txt","r",stdin);
int T=read();
while(T--)
{
n=read();
REP(i,1,n) a[i]=read();
int j=n,cnt=1,last=n+1;
while(cnt<=n)
{
while(j>=1 && a[j]!=cnt) j--;
if(j<1) break;
if(!can(j,last)) break;
cnt+=last-j; last=j;
}
puts(cnt>n?"Yes":"No");
}
return 0;
}
B Nastya and Scoreboard
题意:我们都知道数码管是什么,也知道亮那些表示什么数字。现在有n个数码管,亮的情况已经给出,问再点亮恰好 k 个灯管所能得到的最大的数是多少?
思路:核心思想在于预先计算一个数组 c[i][j],表示第 i 个数往后总共点亮 j 个灯管能否得到一个合法的数,处理完 c 之后就从前往后贪心就可以了。
代码:
#include <bits/stdc++.h>
#define mem(a,b) memset(a,b,sizeof(a))
#define REP(i,a,b) for(int i=(a);i<=(int)(b);i++)
#define REP_(i,a,b) for(int i=(a);i>=(b);i--)
using namespace std;
typedef long long LL;
typedef vector<int> VI;
int read()
{
int x=0,flag=1;
char c=getchar();
while((c>'9' || c<'0') && c!='-') c=getchar();
if(c=='-') flag=0,c=getchar();
while(c<='9' && c>='0') {x=(x<<3)+(x<<1)+c-'0';c=getchar();}
return flag?x:-x;
}
typedef bitset<7> B;
const int maxn=2005;
B a[maxn],b[12];
bool sub(B x,B y) {return (x|y)==y;}
int to[maxn][8],ans[maxn],c[maxn][maxn];
int main()
{
//freopen("input.txt","r",stdin);
int n=read(),k=read();
b[0]=B("1110111"); b[1]=B("0010010"); b[2]=B("1011101"); b[3]=B("1011011");
b[4]=B("0111010"); b[5]=B("1101011"); b[6]=B("1101111"); b[7]=B("1010010");
b[8]=B("1111111"); b[9]=B("1111011");
char s[10];
REP(i,1,n) scanf("%s",s),a[i]=B(s);
REP(i,1,n)
{
REP(j,0,7) to[i][j]=-1;
REP(j,0,9) if(sub(a[i],b[j]))
{
int x=b[j].count()-a[i].count();
to[i][x]=max(to[i][x],j);
}
}
c[n+1][0]=1;
REP_(i,n,1) REP(j,0,k) REP(q,0,7) if(j>=q && to[i][q]>=0 && c[i+1][j-q]) c[i][j]=1;
REP(i,1,n)
{
int maxx=-1,x=0;
REP(j,0,7) if(to[i][j]>=0 && c[i+1][k-j] && to[i][j]>maxx) maxx=to[i][j],x=j;
if(maxx<0) return puts("-1"),0;
ans[i]=maxx; k-=x;
}
REP(i,1,n) printf("%d",ans[i]);
return 0;
}
C Nastya and Unexpected Guest
题意:[0, n] 中有 m 个安全点,其中 0 和 n 一定是安全点(包括在 m 个中),你从 0 开始走路,每单位时间可以走 1,在安全点可以改变方向,非安全点不能改变方向,并且处于绿灯周期时不能在任意地点停留(绿灯时必须一直走)。每个绿灯周期为 g,红灯周期为 r,在绿灯结束红灯开始的时刻,必须停留在安全点。问走到终点 n 的最少时间?
思路:dp+01BFS。设 f[i][j] 表示在第 i 个安全点,绿灯时间还剩 j 的经过的最少绿灯周期数,那么就有点像分层图那样子求最短路就可以了,其中每个 (i, j) (而不是 i )表示一个结点,然后相邻的 (i 和 i+1,i 和 i-1) 之间存在边,当转移之后 j=0 的时候要 +1,其它时候不用。这里用当前最优(队首)去松弛其它结点的时候,因为有可能 +1 或者 不变,所以传统的 BFS(只能处理边权为 1,这里相当于边权可以是 1 也可以是 0),所以要用 01BFS,边权为 1 时被松弛的结点放到队尾,边权为 0 时放到队首。最后统计答案。
代码:
#include <bits/stdc++.h>
#define mem(a,b) memset(a,b,sizeof(a))
#define REP(i,a,b) for(int i=(a);i<=(int)(b);i++)
#define REP_(i,a,b) for(int i=(a);i>=(b);i--)
using namespace std;
typedef long long LL;
typedef vector<int> VI;
int read()
{
int x=0,flag=1;
char c=getchar();
while((c>'9' || c<'0') && c!='-') c=getchar();
if(c=='-') flag=0,c=getchar();
while(c<='9' && c>='0') {x=(x<<3)+(x<<1)+c-'0';c=getchar();}
return flag?x:-x;
}
const int M=1e4+5,G=1005,inf=1e9;
int n,m,d[M],g,r,f[M][G];
void BFS()
{
typedef pair<int,int> P;
deque<P> Q;
Q.push_back(P(1,g));
while(!Q.empty())
{
P p=Q.front(); Q.pop_front();
int i=p.first,j=p.second;
if(i>1)
{
int q=d[i]-d[i-1];
if(j==q && f[i-1][g]>f[i][j]+1)
Q.push_back(P(i-1,g)),f[i-1][g]=f[i][j]+1;
else if(j>q && f[i][j]<f[i-1][j-q])
Q.push_front(P(i-1,j-q)),f[i-1][j-q]=f[i][j];
}
if(i<m)
{
int q=d[i+1]-d[i];
if(j==q && f[i+1][g]>f[i][j]+1)
Q.push_back(P(i+1,g)),f[i+1][g]=f[i][j]+1;
else if(j>q && f[i][j]<f[i+1][j-q])
Q.push_front(P(i+1,j-q)),f[i+1][j-q]=f[i][j];
}
}
}
int main()
{
//freopen("input.txt","r",stdin);
n=read(),m=read();
REP(i,1,m) d[i]=read();
g=read(),r=read();
REP(i,0,m+1) REP(j,0,g+1) f[i][j]=inf;
f[1][g]=0;
sort(d+1,d+m+1);
BFS();
int ans=inf<<1;
REP(j,1,g)
{
if(f[m][j]>=inf) continue;
if(j==g) ans=min(ans,f[m][j]*(g+r)-r);
else ans=min(ans,f[m][j]*(g+r)+g-j);
}
printf("%d",ans>=inf?-1:ans);
return 0;
}
D Nastya and Time Machine
题意:有一棵树,一开始在 1 号结点,时间为 0。假设当前在 u 号结点,时间为 t,我们记现在的状态为 (u, t),接下来有两种操作:(1)转移到 v 号结点((u, v) is a edge),然后 t 变为 t+1,也就是状态变为 (v, t+1);(2)使用时光机,把状态变为 (u, tt),其中 0 ≤ t t < t 0\le tt < t 0≤tt<t 。我们的目标是遍历所有结点,并且使得所有状态的时间的最大值最小,并且要求遍历路径中状态不能重复。给出遍历状态路径。
思路:这是一道神奇的构造题。先证明出这个最大的时间一定不小于最大的度数 m,因为假设结点 u 的度为 d u d_u du ,那么一定存在 d u d_u du 个路径是要转移到 u 的,而转移过去之后的时间最小为 1,又不能重复,所以上述不等式成立。我们现在如果可以构造出一种遍历路径,使得恰好时间最大值为 m,那么这个就是答案。
考虑从状态 (u, t) 开始,转移到 (v, t+1),然后遍历完 v 的子树之后再返回到 (u, t+1),那么对于 u 来说,经过一个 t 就可以把一个儿子的子树遍历完。如果对于每个结点都可以这么做,那么这就是一种方案。事实上也是如此:对于每个结点 u,开始访问 u 的时候已经有了一个 t,然后对于每个儿子,给他们分配 t+1, t+2, …,如果分配过程中超过了 m,那么再(使用时光机)回到 m-d[u] 再开始分配。可以发现,如果分配过程中没有超过 m,那么对于结点 u 遍历完所有儿子之后还要使用一次时光机把时间变成 t-1,然后返回父结点变成 t;如果超过了 m,由于是从 m-d[u] 再次往后分配,最后又会回到 t-1 。非常巧妙。
(感觉讲得好乱……)
代码:
#include <bits/stdc++.h>
#define mem(a,b) memset(a,b,sizeof(a))
#define REP(i,a,b) for(int i=(a);i<=(int)(b);i++)
#define REP_(i,a,b) for(int i=(a);i>=(b);i--)
using namespace std;
typedef long long LL;
typedef vector<int> VI;
int read()
{
int x=0,flag=1;
char c=getchar();
while((c>'9' || c<'0') && c!='-') c=getchar();
if(c=='-') flag=0,c=getchar();
while(c<='9' && c>='0') {x=(x<<3)+(x<<1)+c-'0';c=getchar();}
return flag?x:-x;
}
const int maxn=1e5+5;
VI G[maxn],ans1,ans2;
int d[maxn],n,m;
void add(int u,int t) {ans1.push_back(u); ans2.push_back(t);}
void dfs(int u,int fa,int t)
{
add(u,t);
int tt=t;
for(int v:G[u]) if(v!=fa)
{
tt++;
if(tt>m) tt=m-d[u],add(u,tt),tt++;
dfs(v,u,tt);
}
if(fa)
{
if(tt!=t-1) tt=t-1,add(u,tt);
add(fa,t);
}
}
int main()
{
//freopen("input.txt","r",stdin);
n=read();
REP(i,1,n-1)
{
int u=read(),v=read();
G[u].push_back(v); G[v].push_back(u);
d[u]++; d[v]++;
m=max(m,max(d[u],d[v]));
}
dfs(1,0,0);
printf("%d\n",ans1.size());
REP(i,0,ans1.size()-1) printf("%d %d\n",ans1[i],ans2[i]);
return 0;
}
F
题意:
思路:
代码: