A.Abstract Picture Gym - 100917A
分析:由于最后刷的一笔肯定使得某一行或者是某一列均为相同的颜色。
因此我们可以在一开始找到所有的行或者列,他们的颜色全都一样,把这样的行或列加入到队列里面去。
我们处理在队列里面的行或者列:把它取出来,并且把该笔刷成的颜色全都变成’?’(即可变颜色)。然后检查是否有新的行或者列变成颜色均相同了,如果有的话,继续加入队列中进行处理。
队列出队的顺序反过来即为答案。
具体实现细节略。
F - Find the Length Gym - 100917F
分析:因为不存在重复边,那么一个最短简单环,至少需要有三个点组成,而删掉最短简单环上的一条边 那么剩下的一条路径一定是删去 之后的从 到 的最短路。
对于一定包含点 的最短简单环,我们枚举这里面假装删掉的那条边,那么会出现下面两种情况
一、这条边的一个端点是
这种情况下,设另一端的端点为 ,如果 到 的最短路就不是这条边,那么很好,从 到 的最短路 边 就是一个可行结果,更新包含 的最短简单环答案;而如果从 到 的最短路就是这条边,那么这种情况我们直接扔掉,不做处理,因为这种情况可以由枚举其他边的时候解决。
二、这条边的两个端点 均非
这种情况下,从 到 的最短路还要包含 ,那么只能是从 到 的最短路 从 到 的最短路,这里千万要注意两条最短路不能有重复边。否则从 到 的最短路将不会包含点 。
具体做法:
枚举点 ,跑一边Dijkstra,得到一颗最短路径树,然后根据第一种情况更新一次答案。根据第二种情况更新答案的时候注意,判断两条路不包含重复边的方法可以用 ,当然也可以用其他的方法。
#include <iostream>
#include <cstdio>
#include <queue>
using namespace std;
typedef pair<int,int> pii;
const int N = 400;
const int inf = 1e8;
int dist[N];
int G[N][N];
int n;
int fa[N];
int find(int x){
return x == fa[x] ? x : fa[x] = find(fa[x]);
}
void dij(int s){
for(int i = 1;i <= n;++i) dist[i] = inf,fa[i] = i;
dist[s] = 0;
priority_queue<pii> Q;
Q.push({0,s});
while(!Q.empty()){
pii p = Q.top();Q.pop();
int u = p.second;
int d = -p.first;
if(d > dist[u]) continue;
for(int v = 1;v <= n;++v){
if(v == u) continue;
if(dist[v] > dist[u] + G[u][v]){
dist[v] = dist[u] + G[u][v];
if(u != s) fa[v] = u;
Q.push({-dist[v],v});
}
}
}
}
#define pr(x) cout<<#x<<":"<<x<<endl
int main(){
cin>>n;
for(int i = 1;i <= n;++i){
for(int j = 1;j <= n;++j){
scanf(" %d",&G[i][j]);
if(G[i][j] == -1) G[i][j] = inf;
}
}
for(int s = 1;s <= n;++s){
int ans = inf;
dij(s);
for(int i = 1;i <= n;++i){
if(fa[i] != i){
if(dist[i] + G[i][s] < ans)
ans = dist[i] + G[i][s];
}
}
for(int i = 1;i <= n;++i){
if(i == s) continue;
for(int j = 1;j <= n;++j){
if(j == s || find(i) == find(j)) continue;
if(dist[i]+dist[j]+G[i][j] < ans)
ans = dist[i]+dist[j]+G[i][j];
}
}
ans = ans == inf ? -1:ans;
printf("%d\n",ans);
}
return 0;
}
I - Interactive Casino Gym - 100917I
这道题很有意思。
我们把所有的种子每个都用随机数生成器计算出前几个值,并把每个值得到的输赢用01串存起来,实践发现当计算到第38层的时候,我们就可以把每个种子的01串给区分开了。对于每个种子,01串的长度为38。
我们将这所有的01串放入一个Trie树里面去。一开始一直用猜1来试,可以根据反馈结果确定这次的位。38轮以后我们就可以找得到随机数种子了。找到随机数种子这道题基本就结束了。
J - Judgement Gym - 100917J
我们用背包dp求出在第一种方案下的权值和为 时候所能得到第二种方案下权值和的最大值 ,只要出现 而 的情况,那么就输出”NO”并且打印方案。反过来再做一遍就好了。
这里注意这道题要使用滚动数组,这样的话记录方案就会变得麻烦,为了保存信息,需要使用一个长为100的bitset来存方案。
#include <iostream>
#include <cstdio>
#include <assert.h>
#include <cstring>
#include <bitset>
using namespace std;
const int N = 1e6+10;
int n,upb[2],val[2][N];
int dp[2][N];
bitset<101> way[2][N];
int vis[111];
int main(){
scanf("%d",&n);
scanf("%d",&upb[0]);
for(int i = 1;i <= n;++i) scanf("%d",&val[0][i]);
scanf("%d",&upb[1]);
for(int i = 1;i <= n;++i) scanf("%d",&val[1][i]);
int cc = 0 ;
for(int cas = 0;cas < 2;++cas){
for(int i = 1;i <= n;++i){
for(int j = upb[cas]-1;j >= val[cas][i];j--){
if(dp[cas][j-val[cas][i]] + val[cas^1][i] > dp[cas][j]){
dp[cas][j] = dp[cas][j-val[cas][i]] + val[cas^1][i];
//map[cas][j] = i;
way[cas][j] = way[cas][j-val[cas][i]];
way[cas][j].set(i);
if(dp[cas][j] >= upb[cas^1]){
//output(cas,j);
puts("NO");
for(int i = 1;i <= n;++i){
cout<<way[cas][j][i];
}
return 0;
}
}
}
}
}
puts("YES");
return 0;
}