2016-2017 ACM-ICPC Northwestern European Regional Programming Contest (NWERC 2016)
A、给你一堆十进制数字,允许前导零,大小比较的方式同字典序
你需要更改最少的数位,使得整个序列单调不增
究极神秘的dp:
\(dp[i+1][j+k]=cal(dp[i][j])(0<=k<=2*n)\)
其中:
\(dp[i][j]\)表示前\(i\)个数字更改了\(j\)次之后所能得到的最小“数字”
这意味着dp数组里储存的是一个字符串
接下来是窒息的\(cal()\)函数,用于计算\(k\)步之内能得到的最小字符串,并且还要满足\(>=dp[i][j]\)
cal函数实际上贪心地找即可,先看一下能不能把每一位都设置得跟\(dp[i][j]\)一样
不能的话就向前找到第一位不是\(9\)的,然后把这儿\(+1\)即可
最后别忘了剩下的所有数位置\(0\),因为要保证最小
dp的初值弄一个全0的合法串即可,还要记录一下转移路径方便输出
代码:
#include <bits/stdc++.h>
#define FAST ios_base::sync_with_stdio(0);cin.tie(0),cout.tie(0);
using namespace std;
const int N=45;
int n,m;
string s[N];
string dp[N][N*2];
int pre[N][N*2];
bool can[N][N*2];
bool gao(string &p,string s,string t,int k)
{
p=s;
int cnt=k,i=0;
for(;i<m&&cnt>0;++i)
{
if(p[i]!=t[i])
{
p[i]=t[i];
cnt--;
}
}
if(p>=t) return 1;
for(--i;i>=0&&p[i]=='9';--i);//
if(i<0) return 0;
p=s;
cnt=k;
for(int j=0;j<i;++j)
{
if(p[j]!=t[j])
{
p[j]=t[j];
cnt--;
}
}
if(p[i]!=t[i]+1)
{
p[i]=t[i]+1;
cnt--;
}
for(int j=i+1;j<m&&cnt>0;++j)
{
p[j]='0';
cnt--;
}
return 1;
}
void gao2()
{
can[0][0]=1;
for(int i=0;i<m;++i)
dp[0][0]+='0';
}
int main()
{
FAST
cin>>n>>m;for(int i=1;i<=n;++i) cin>>s[i];
gao2();
for(int i=0;i+1<=n;++i)
{
for(int j=0;j<=2*n;++j)
{
if(!can[i][j]) continue;
string p=s[i+1];
for(int k=0;j+k<=2*n;++k)
{
if(gao(p,s[i+1],dp[i][j],k)&&(!can[i+1][j+k]||dp[i+1][j+k]>p))
{
can[i+1][j+k]=1;
pre[i+1][j+k]=j;
dp[i+1][j+k]=p;
}
}
}
}
stack<string> ans;
for(int j=0;j<=2*n;++j)
{
if(can[n][j])
{
for(int i=n;i>=1;--i)
{
ans.push(dp[i][j]);
j=pre[i][j];
}
break;
}
}
while(!ans.empty())
{
cout<<ans.top()<<endl;
ans.pop();
}
return 0;
}
B、有向图最长链,本来是NP问题,但是有限定环的大小不超过5
那么就先缩点,然后SCC内暴力转移,再把dp值转移给相邻的SCC,拓扑排序即可
代码:
#include <bits/stdc++.h>
using namespace std;
const int N=1e5+5;
const int inf=0x3f3f3f3f;
int n,m,x[N*10],y[N*10],tmp[N*10];
vector<int> e[N],scc[N],e2[N];
int dfn[N],low[N],timer=0,c[N],color=0;
int deg[N],dp[N];
bool vis[N];
stack<int> s;
void add(int x,int y){e[x].push_back(y);}
void add2(int x,int y){e2[x].push_back(y);}
void tarjan(int u)
{
dfn[u]=low[u]=++timer;
s.push(u);
vis[u]=1;
for(auto v:e[u])
{
if(!dfn[v])
{
tarjan(v);
low[u]=min(low[u],low[v]);
}
else if(vis[v]) low[u]=min(low[u],dfn[v]);
}
if(low[u]==dfn[u])
{
++color;
int t;
do
{
t=s.top();
s.pop();
c[t]=color;
vis[t]=0;
scc[color].push_back(t);
}
while(u!=t);
}
}
void gao2(int x,int cnt)
{
vis[x]=1;
for(auto y:e[x])
{
if(vis[y]==0&&c[x]==c[y])//
gao2(y,cnt+1),dp[y]=max(dp[y],cnt+1);
else if(c[x]!=c[y])//
dp[y]=max(dp[y],1+cnt);
}
vis[x]=0;
}
void gao()
{
memset(vis,0,sizeof(vis));
for(int i=1;i<=m;++i)
{
if(c[x[i]]!=c[y[i]])
add2(c[x[i]],c[y[i]]),deg[c[y[i]]]++;
}
queue<int> q;
for(int i=1;i<=color;++i)
if(deg[i]==0)
q.push(i);
while(!q.empty())
{
int x=q.front();
q.pop();
for(auto i:scc[x])
tmp[i]=dp[i];
for(auto i:scc[x])
gao2(i,tmp[i]);
for(auto y:e2[x])
{
deg[y]--;
if(deg[y]==0)
q.push(y);
}
}
// for(int i=1;i<=n;++i) printf("? %d ",dp[i]);
int ans=-inf;
for(int i=1;i<=n;++i) ans=max(ans,dp[i]);
printf("%d",ans+1);
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=m;++i)
{
scanf("%d%d",&x[i],&y[i]);
add(x[i],y[i]);
}
for(int i=1;i<=n;++i)
if(!dfn[i])
tarjan(i);
gao();
return 0;
}
C、签到,垂直方向和水平方向速度不变,在某些区域水平方向会暂时加速,问你初速度是多少才能恰好抵达某个点
实际上就是解一元一次方程
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
double x,y,f;
int n,l,r;
int main()
{
scanf("%lf%lf",&x,&y);
scanf("%d",&n);
double sum=0.0,tmp=0.0;
for(int i=1;i<=n;++i)
{
scanf("%d%d%lf",&l,&r,&f);
sum+=(r-l)*f;
tmp+=r-l;
}
sum+=y-tmp;
printf("%.10lf",x/sum);
return 0;
}
E、签到,题意比较迷,老师会选一个房间开始发卷子,每个房间会发出这个房间里的人数张卷子,然后再收进来人数张卷子,问你一圈下来会不会有人收到自己的卷子
从人数最多的房间开始发,模拟一遍判断第一个房间的人有没有拿到自己卷子即可
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=35;
const int inf=0x3f3f3f3f;
int n;
pair<int,int> p[N];
vector<int> ans;
queue<int> q;
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;++i)
{
scanf("%d",&p[i].first);
p[i].second=i;
}
sort(p+1,p+1+n);
ans.push_back(p[n].second);
for(int i=1;i<=p[n].first;++i)
q.push(p[n].second);
for(int i=n-1;i>=1;--i)
{
for(int j=1;j<=p[i].first;++j)
q.pop();
for(int j=1;j<=p[i].first;++j)
q.push(p[i].second);
ans.push_back(p[i].second);
}
bool can=1;
int f;
while(!q.empty())
{
f=q.front();
if(f==p[n].second)
{
can=0;
break;
}
q.pop();
}
if(can)
{
for(auto i:ans) printf("%d ",i);
}
else
puts("impossible");
return 0;
}
F、队友写的二分
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int n,a[1000005],b[1000005];
bool check(int x)
{
vector<int> A,B;
for(int i=0;i<n;i++)
{
if(a[i]>x) A.push_back(a[i]);
if(b[i]>x) B.push_back(b[i]);
}
if(A.size()%2||B.size()%2) return false;
for(int i=0;i<A.size();i+=2)
if(A[i]!=A[i+1]) return false;
for(int i=0;i<B.size();i+=2)
if(B[i]!=B[i+1]) return false;
return true;
}
int main()
{
scanf("%d",&n);
for(int i=0;i<n;i++)
scanf("%d",a+i);
for(int i=0;i<n;i++)
scanf("%d",b+i);
int l=0,r=1000000000;
while(l<r)
{
int m=l+r>>1;
if(check(m)) r=m; else l=m+1;
}
printf("%d",l);
return 0;
}
H、队友写的格雷码
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int n;
ll a[100],b[100],A,B;
int main()
{
scanf("%d",&n);
for(int i=0;i<n;i++)
scanf("%1lld",&a[i]);
for(int i=0;i<n;i++)
scanf("%1lld",&b[i]);
for(int i=1;i<n;i++)
a[i]^=a[i-1],b[i]^=b[i-1];
for(int i=0;i<n;i++)
{
A<<=1,B<<=1;
A+=a[i],B+=b[i];
}
printf("%lld",max(0ll,abs(A-B)-1));
return 0;
}
I、怪怪最短路,有两种矿井,每个点都可能是其中的一种,求从1开始到A矿井和B矿井经过的点数最少是多少
考虑跑三次dij,分别从1、A的超级源点、B的超级源点三个位置开始,求出1到所有点、所有A到所有点、所有B到所有点的最短路
注意A和B的超级源点要跑反向边,因为是有向图
然后枚举每个点即可,答案是三个距离之和的最小值
正确性有个感性的证明,答案点应该是一个“三岔口”,它经过的重复点数最少
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
int n,m,C,O,diso[100005],disc[100005],dis[100005],vis[100005],o[100005],c[100005];
struct edge
{
int y,v;
edge(int Y,int V):y(Y),v(V){}
};
vector<edge> e[100005],rev[100005];
inline void add(int x,int y,int v)
{
e[x].emplace_back(y,v);
}
inline void revadd(int x,int y,int v)
{
rev[x].emplace_back(y,v);
}
void dij(int dis[],vector<edge> e[],int s)
{
//memset(dis,0x3f,sizeof(dis));
memset(vis,0,sizeof(vis));
dis[s]=0;
priority_queue<pii,vector<pii>,greater<pii>> q;
q.emplace(0,s);
while(!q.empty())
{
int x=q.top().second;q.pop();
if(vis[x]) continue;
vis[x]=1;
for(auto y:e[x])
if(dis[x]+y.v<dis[y.y])
{
dis[y.y]=dis[x]+y.v;
q.emplace(dis[y.y],y.y);
}
}
}
int main()
{
scanf("%d%d%d",&n,&O,&C);
for(int i=0,x;i<O;i++)
{
scanf("%d",&x);
revadd(n+1,x,0);
}
for(int i=0,x;i<C;i++)
{
scanf("%d",&x);
revadd(n+2,x,0);
}
for(int i=1,tot,x;i<=n;i++)
{
scanf("%d",&tot);
for(int j=0;j<tot;j++)
scanf("%d",&x),add(i,x,1),revadd(x,i,1);
}
memset(dis,0x3f,sizeof(dis));
memset(disc,0x3f,sizeof(disc));
memset(diso,0x3f,sizeof(diso));
dij(diso,rev,n+1);
dij(disc,rev,n+2);
dij(dis,e,1);
ll ans=0x3f3f3f3f;
for(int i=1;i<=n;i++)
ans=min(ans,(long long)diso[i]+disc[i]+dis[i]);
if(ans>=0x3f3f3f3f) printf("impossible"); else printf("%lld",ans);
return 0;
}
J、队友说似乎是个大模拟?欧洲人的英语确实有点迷惑
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int main()
{
int n,q,s;
ll sumd=0;
ll suma=0;
int tar[105]={0};
ll qsize[35]={0};
ll qleft[35]={0};
ll buffer=0;
scanf("%d %d %d",&n,&q,&s);
for(int i=1;i<=s;i++)
{
scanf("%d",tar+i);
}
for(int i=1;i<=q;i++)
{
scanf("%lld",qsize+i);
qleft[i]=qsize[i];
}
ll par,tmp;
for(int i=0;i<n;i++)
{
scanf("%lld",&tmp);
sumd+=tmp;
ll qthis[35]={0};
for(int j=1;j<=s;j++)
{
scanf("%lld",&par);
qthis[tar[j]]+=par;
suma+=par;
}
for(int j=1;j<=q;j++)
{
if(qthis[j]>qsize[j])
{
cout<<"impossible";
return 0;
}
}
for(int j=1;j<=q;j++)
{
qleft[j]-=qthis[j];
if(qleft[j]<0)
{
buffer+=qleft[j];
qleft[j]=0;
if(buffer<0)
{
cout<<"impossible";
return 0;
}
}
}
buffer+=tmp;
}
if(sumd>=suma)
{
cout<<"possible";
}
else
{
cout<<"impossible";
}
return 0;
}