【题解】Codeforces Round 766(CF1627)
所有题都有奥~~~
A.Not Shading
题目分析:
如果 \((r,c)\) 就是黑色的,那就是 \(0\) 次操作。
否则如果 \(r\) 行或者 \(c\) 列有黑色的那就是 \(1\) 次操作。
否则如果存在黑色格子那就是 \(2\) 次操作。
如果不存在黑色格子就是无解
代码:
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N = 100;
char s[N][N];
int main(){
int t;scanf("%d",&t);
while(t--){
int n,m,r,c;scanf("%d%d%d%d",&n,&m,&r,&c);
for(int i=1; i<=n; i++) scanf("%s",s[i]+1);
if(s[r][c] == 'B'){
printf("0\n");
continue;
}
bool flag = false;
for(int i=1; i<=n; i++) if(s[i][c] == 'B') flag = true;
for(int i=1; i<=m; i++) if(s[r][i] == 'B') flag = true;
if(flag){
printf("1\n");
continue;
}
for(int i=1; i<=n; i++){
for(int j=1; j<=m; j++){
if(s[i][j] == 'B') flag = true;
}
}
if(flag) printf("2\n");
else printf("-1\n");
}
return 0;
}
B.Not Sitting
题目分析:
要使得距离最大,显然 Tina 会选择四个角上的一个。
看这个数据范围,其实我们可以直接枚举得到 Rahul 选择每一个格子时的最优距离,那么 Tina 必然每一次都会染色最优距离最短的一个。
所以排序之后输出就好了。
代码:
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N = 2e5+5;
int a[N];
int d(int x1,int y1,int x2,int y2){
return abs(x1 - x2) + abs(y1 - y2);
}
int main(){
int t;scanf("%d",&t);
while(t--){
int n,m;scanf("%d%d",&n,&m);
int cnt = 0;
for(int i=1; i<=n; i++){
for(int j=1; j<=m; j++){
a[++cnt] = max(max(d(i,j,1,1),d(i,j,1,m)),max(d(i,j,n,1),d(i,j,n,m)));
}
}
sort(a+1,a+cnt+1);
for(int i=1; i<=cnt; i++) printf("%d ",a[i]);
printf("\n");
}
return 0;
}
C.Not Assigning
题目分析:
要使得两个质数之和为质数那么只有一种可能:\(2 + x(x \not= 2)\),因为除了 \(2\) 之外的质数都是奇数,奇数和奇数之和一定为偶数,且这个偶数一定大于 \(2\)。
所谓的距离不超过 \(2\) 的路径和全部为质数,也就是说对于一个点其相连的所有边的权值都为质数且任选两个和也是质数。
可以发现当一个点相连的边数大于 \(2\) 就一定无解。
因为如果有解,根据上面的结论则必然有:\(2 + x_1\)、\(2 + x_2\)、\(x_1 + x_2\) 都为质数,且 \(x_1,x_2 \not= 2\),显然是矛盾的。
所以最后得到的就是一条链,链的话就直接 \(2\) \(5\) 地填就好了。
代码:
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N = 1e6+5;
int ans[N],deg[N];
vector<pair<int,int> > e[N];
void dfs(int now,int fath,int val,int num){
ans[num] = val;
for(auto i : e[now]){
if(i.first == fath) continue;
dfs(i.first,now,val == 2 ? 5 : 2,i.second);
}
}
int main(){
int t;scanf("%d",&t);
while(t--){
int n;scanf("%d",&n);
for(int i=1; i<=n; i++) e[i].clear(),deg[i] = 0;
for(int i=1; i<n; i++){
int from,to;scanf("%d%d",&from,&to);
e[from].push_back({to,i});e[to].push_back({from,i});
deg[from]++;deg[to]++;
}
int mx = 0;
for(int i=1; i<=n; i++) mx = max(mx,deg[i]);
if(mx >= 3){
printf("-1\n");
continue;
}
int val = 2;
for(auto i : e[1]){
dfs(i.first,1,val,i.second);
val = 5;
}
for(int i=1; i<n; i++) printf("%d ",ans[i]);
printf("\n");
}
return 0;
}
D.Not Adding
题目分析:
看到值域那么小不难想到其实可以枚举每一个数判断是否可以被弄出来。
设现在枚举的数为 \(x\),而我们知道 \(\gcd(\gcd(a,b),c) = \gcd(a,b,c)\),所以其实就是让我们选出一个子序列使得它的 \(\gcd = x\) 啊。
因为要使得它们的 \(\gcd = x\),也就是说对于其中的任意一个数都要是 \(x\) 的倍数,所以直接将 \(x\) 的所有倍数都拿出来取一个 \(\gcd\),如果结果为 \(x\) 则可以,如果不是则一定不可以。
代码:
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N = 2e6+5;
const int MAX = 1e6;
int a[N];
bool vis[N];
int main(){
int n;scanf("%d",&n);
for(int i=1; i<=n; i++) scanf("%d",&a[i]),vis[a[i]] = true;
int ans = 0;
for(int i=1; i<=MAX; i++){
int now = 0;
for(int j=i; j<=MAX; j+=i){
if(vis[j]) now = __gcd(now,j);
if(now == i) break;
}
if(now == i && !vis[i]) ans++;
}
printf("%d\n",ans);
return 0;
}
E.Not Escaping
题目分析:
这个题一眼最短路啊,但是点数边数太多了咋办。
很显然只有 \((1,1)\)、\((n,m)\)、梯子的端点是有用的,所以直接保留这些点然后建边跑最短路就好了。(我这个傻逼一开始竟然直接写了 Dijkstra,但是他有负边啊喂)
这样建出来是一个网格图,会把 SPFA 卡掉,那么就考虑怎么换种办法。
其实我们也可以感受到,这么一个有性质的图去跑一般图的最短路算法好浪费啊,因为我们每一行的最短路必然不存在回头路,也就是说我们可以将每一行直接拆分为从左向右和从右向左的两个 DAG,对于两个 DAG 分别求最短路,这一步显然可以做到严格 \(O(n)\),然后取最优值就是答案。
代码:
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 1e6+5;
const int INF = 1e18;
struct node{
int from,to,val;
node(){}
node(int _from,int _to,int _val){
from = _from,to = _to,val = _val;
}
};
int dp[N],h[N],dis[N],a[N],b[N],c[N],d[N],res[N];
vector<int> v[N];
vector<node> g[N];
map<pair<int,int>,int> mp;
signed main(){
// freopen("in.txt","r",stdin);
// freopen("out.txt","w",stdout);
int t;scanf("%lld",&t);
while(t--){
int n,m,k,tot;scanf("%lld%lld%lld",&n,&m,&k);
for(int i=1; i<=n; i++) scanf("%lld",&res[i]);
mp[{1,1}] = 1;mp[{n,m}] = 2;tot = 2;
v[1].push_back(1);v[n].push_back(m);
for(int i=1; i<=k; i++){
scanf("%lld%lld%lld%lld%lld",&a[i],&b[i],&c[i],&d[i],&h[i]);
if(!mp.count({a[i],b[i]})) mp[{a[i],b[i]}] = ++tot;
if(!mp.count({c[i],d[i]})) mp[{c[i],d[i]}] = ++tot;
v[a[i]].push_back(b[i]);v[c[i]].push_back(d[i]);
g[a[i]].push_back(node(mp[{a[i],b[i]}],mp[{c[i],d[i]}],-h[i]));
}
for(int i=1; i<=tot; i++) dp[i] = INF;
dp[1] = 0;
for(int i=1; i<=n; i++){
sort(v[i].begin(),v[i].end());
int tmp = v[i].size();
for(int j=0; j<tmp-1; j++){
int x = v[i][j],y = v[i][j+1];
dp[mp[{i,y}]] = min(dp[mp[{i,y}]],dp[mp[{i,x}]] + (y-x) * res[i]);
}
for(int j=tmp-1; j>0; j--){
int x = v[i][j],y = v[i][j-1];
dp[mp[{i,y}]] = min(dp[mp[{i,y}]],dp[mp[{i,x}]] + (x-y) * res[i]);
}
for(auto j : g[i]){
dp[j.to] = min(dp[j.to],dp[j.from] + j.val);
}
}
if(dp[2] >= 1ll * 1e16) printf("NO ESCAPE\n");
else printf("%lld\n",dp[2]);
mp.clear();
for(int i=1; i<=n; i++) v[i].clear(),g[i].clear();
}
return 0;
}
F.Not Splitting
题目分析:
有一个神仙的性质是:这两个图形以及切的线一定关于 \((\frac{k}{2},\frac{k}{2})\) 对称。
不会证明就不证了,但是感性理解一下应该还是好理解的。
既然有了这个结论也就是说我们可以从 \((\frac{k}{2},\frac{k}{2})\) 开始只求一边是怎么切的,另一边就是直接对称过去。
我们要求最多的不经过其实就是最少的经过,而这个题显然支持 \(O(n^2)\) 的建边,所以直接考虑求出 每一个点怎么走会造成多少的贡献,建出来图之后求最短路就好了。
这个贡献不要忘记对称边的贡献。
代码:
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1200;
const int INF = 1e9+5;
struct edge{
int nxt,to,val;
edge(){}
edge(int _nxt,int _to,int _val){
nxt = _nxt,to = _to,val = _val;
}
}e[2 * MAXN * MAXN];
int k,n,tot,head[MAXN * MAXN],dis[MAXN * MAXN],cnt[MAXN][MAXN][5];
bool vis[MAXN * MAXN];
//1 右边:2 下面的右边;3 下边;4 右面的下边
// 1 2 3 4,分别对应去左、右、上、下的贡献
int dx[] = {0,-1,1,0,0};
int dy[] = {0,0,0,-1,1};
void add_edge(int from,int to,int val){
e[++tot] = edge(head[from],to,val);
head[from] = tot;
}
int N(int x,int y){
return x * (k+1) + y + 1;
}
int get(int x,int y,int p){
int tx = x - k / 2,ty = y - k / 2;
tx = -tx,ty = -ty;
tx += k/2;ty += k/2;
if(p == 1) p = 2;
else if(p == 2) p = 1;
else if(p == 3) p = 4;
else if(p == 4) p = 3;
return cnt[tx][ty][p];
}
int main(){
// freopen("in.txt","r",stdin);
// freopen("out.txt","w",stdout);
int T;scanf("%d",&T);
while(T--){
scanf("%d%d",&n,&k);
for(int i=0; i<=k; i++){
for(int j=0; j<=k; j++){
for(int p=1; p<=4; p++){
cnt[i][j][p] = 0;
}
}
}
for(int i=1; i<=n; i++){
int x1,y1,x2,y2;scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
if(x1 == x2){
if(y1 > y2) swap(y1,y2);
cnt[x1][y1][1]++;
cnt[x1-1][y1][2]++;
}
else{
if(x1 > x2) swap(x1,x2);
cnt[x1][y1][3]++;
cnt[x1][y1-1][4]++;
}
}
int s = N(k/2,k/2),t = N(k,k) + 1;
for(int i=0; i<=k; i++){
for(int j=0; j<=k; j++){
if(i == 0 || j == 0 || i == k || j == k) add_edge(N(i,j),t,0);
else{
for(int p=1; p<=4; p++){
int x = i + dx[p],y = j + dy[p];
if(x < 0 || x > k || y < 0 || y > k) continue;
add_edge(N(i,j),N(x,y),cnt[i][j][p]+get(i,j,p));
// printf("%d %d %d\n",N(i,j),N(x,y),cnt[i][j][p] + get(i,j,p));
}
}
}
}
for(int i=0; i<=t; i++) dis[i] = INF;
priority_queue<pair<int,int> > q;
dis[s] = 0;q.push({-dis[s],s});
while(!q.empty()){
int now = q.top().second;q.pop();
if(vis[now]) continue;
vis[now] = true;
for(int i = head[now]; i;i = e[i].nxt){
int to = e[i].to;
if(dis[to] > dis[now] + e[i].val){
dis[to] = dis[now] + e[i].val;
q.push({-dis[to],to});
}
}
}
printf("%d\n",n - dis[t]);
for(int i=0; i<=t; i++) head[i] = 0,vis[i] = false;
tot = 0;
}
return 0;
}