时间:2017/9/8 题目8/10 Rank 5/150
体会:三星的题目和国内区域赛差距大,大多数题读懂题意就能做,所以静心读题是关键,套路性太深。
A:
题意:给出一个算式,算式中的数字用大写字母代替。每个字母只能代替一个数字,一个数字也只能被一个字母代替。有多少种数字分配方式可以使得这个算式成立?
解法:爆搜。一共不超过十个字母,把这十个字母列出来然后进行dfs分配数字,分配完后就进行验证是否可以满足式子,还要考虑累加的n-1个数不能是前导0,。
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
int len[20], n, cnt;
char str[20][20];
bool used[30];
int vis[30];
struct node{
char ch;
int num, flag;//flag标记前导0
}a[20];
bool check()
{
int sum = 0;
for(int i=1; i<n; i++){
int t = 0;
for(int j=0; j<len[i]; j++)
t = t*10 + vis[str[i][j]-'A'];
sum += t;
}
int ans = 0;
for(int i=0; i<len[n]; i++)
ans = ans*10 + vis[str[n][i]-'A'];
return sum == ans;
}
LL dfs(int pos)
{
LL ans=0;
if(pos>=cnt) return check();
for(int i=0; i<=9; i++){
if(used[i]==false){
if(a[pos].flag == 0 && i == 0) continue;
a[pos].num = i;
vis[a[pos].ch-'A'] = i;
used[i] = 1;
ans += dfs(pos+1);
used[i] = 0;
}
}
return ans;
}
int main()
{
while(~scanf("%d", &n))
{
cnt = 0;
memset(vis, 0, sizeof(vis));
for(int i=1; i<=n; i++){
scanf("%s", str[i]);
len[i] = strlen(str[i]);
for(int j=0; j<len[i]; j++){
if(!vis[str[i][j]-'A']){
a[cnt].flag = 1;
a[cnt++].ch = str[i][j], vis[str[i][j]-'A'] = 1;
}
if(j==0){
for(int k=0; k<cnt; k++){
if(a[k].ch == str[i][0]){
a[k].flag = 0;
}
}
}
}
}
LL ans = 0;
memset(used,false,sizeof(used));
ans = dfs(0);
printf("%lld\n", ans);
}
return 0;
}
B:
题意:计算 1 和 n 两点间最短路的路径和的两倍
解法:计算最短路, 枚举没一条边到两端的距离加上本身长度,判断是等于最短路长度,若是的,那么这条边可以当做最短路的一条边,注意好像spfa会TLE。我是看图猜的题意,
导致很快就写出了这题,以后比赛要注意,猜错容易崩盘。
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
const int maxn = 10005;
const int maxm = 250000 * 2;
struct EDGE
{
int to, next, len;
EDGE() {}
EDGE(int to, int next, int len) :to(to), next(next), len(len) {}
}edge[maxm];
int head[maxn],edgecnt;
int dis[2][maxn];
int n;
struct node
{
int u, dis;
node(int u,int dis):u(u),dis(dis){}
bool operator < (const node &b) const
{
return dis > b.dis;
}
};
void init()
{
memset(head, -1, sizeof(head));
edgecnt = 0;
}
void add(int s, int t, int l)
{
edge[edgecnt] = EDGE(t, head[s], l);
head[s] = edgecnt++;
}
void spfa(int st, int dis[])
{
for (int i = 1; i <= n; i++) dis[i] = INT_MAX;
dis[st] = 0;
priority_queue<node> q;
static bool f[maxn];
memset(f, 0, sizeof(f));
q.emplace(st, 0);
while (!q.empty())
{
int u = q.top().u;
q.pop();
if (f[u]) continue;
f[u] = 1;
for (int i = head[u]; ~i; i = edge[i].next)
{
int v = edge[i].to;
if (dis[v] > dis[u] + edge[i].len)
{
dis[v] = dis[u] + edge[i].len;
q.emplace(v, dis[v]);
}
}
}
}
int main()
{
int m;
while (~scanf("%d %d", &n, &m))
{
init();
for (int i = 1; i <= m; i++)
{
int s, t, l;
scanf("%d %d %d", &s, &t, &l);
s++;
t++;
add(s, t, l);
add(t, s, l);
}
spfa(1, dis[0]);
spfa(n, dis[1]);
int len = dis[0][n];
int ans = 0;
for (int u = 1; u <= n; u++)
{
for (int i = head[u]; ~i; i = edge[i].next)
{
int v = edge[i].to;
if (v == u) continue;
if (dis[0][u] + edge[i].len + dis[1][v] == len)
ans += edge[i].len;
}
}
printf("%d\n", 2 * ans);
}
return 0;
}
C:
题意:给你N个整数和M个整数,问这M个数中,有几个数可以表达成那N个整数中一个或者两个整数的和。
解法:简单FFT即可。发现我的FFT大概慢100ms,下次要注意。
#include <bits/stdc++.h>
using namespace std;
const double PI = acos(-1);
const int maxn = 800010;
typedef complex <double> Complex;
void rader(Complex *y, int len) {
for(int i = 1, j = len / 2; i < len - 1; i++) {
if(i < j) swap(y[i], y[j]);
int k = len / 2;
while(j >= k) {j -= k; k /= 2;}
if(j < k) j += k;
}
}
void fft(Complex *y, int len, int op) {
rader(y, len);
for(int h = 2; h <= len; h <<= 1) {
double ang = op * 2 * PI / h;
Complex wn(cos(ang), sin(ang));
for(int j = 0; j < len; j += h) {
Complex w(1, 0);
for(int k = j; k < j + h / 2; k++) {
Complex u = y[k];
Complex t = w * y[k + h / 2];
y[k] = u + t;
y[k + h / 2] = u - t;
w = w * wn;
}
}
}
if(op == -1) for(int i = 0; i < len; i++) y[i] /= len;
}
Complex x1[maxn], x2[maxn];
int n, q, a[maxn];
int sum[maxn];
int main()
{
while(~scanf("%d", &n))
{
memset(a, 0, sizeof(a));
int len1 = 0;
for(int i=0; i<n; i++){
int x;
scanf("%d", &x);
a[x]=1;
len1 = max(len1, x);
}
len1++;
int len=1;
while(len<len1*2) len<<=1;
a[0] = 1;
for(int i=0; i<len1; i++) x1[i] = Complex(a[i], 0);
for(int i=0; i<len1; i++) x2[i] = Complex(a[i], 0);
fft(x1, len, 1);
fft(x2, len, 1);
for(int i=0; i<len; i++)
x1[i] = x1[i]*x2[i];
fft(x1, len, -1);
for(int i=0; i<len; i++)
sum[i] = (int)(x1[i].real()+0.5);
scanf("%d", &q);
int ans = 0;
while(q--)
{
int x;
scanf("%d", &x);
if(sum[x]>0) ans++;
}
printf("%d\n", ans);
}
return 0;
}
D:
题意:在一个借书会中,若A喜欢B的书,B喜欢C的书,C喜欢A的书,这样每个人都可以借到书了,输出 yes,若 A喜欢B的书,B喜欢C的书,C喜欢D的书,这样就不能保证每个人都借到书,输出 no
解法:这个可以看做二分匹配,将每一个点拆分为两个点A B,自己与自己不连边,添加一个源点和一个汇点,源点到A的流的大小为 1 B到汇点的流的大小为 1 ,问是否可以匹配成功让大家都借到书,直接匈牙利也是可以的。一眼想到二分匹配,但是队友因为数据范围说不可能,自己有一点点动摇,然后写个网络流。下次要注意,不能全听队友,要坚持自己。
#include <bits/stdc++.h>
using namespace std;
const int maxn = 100100;
const int maxm = 400100;
const int inf = 0x3f3f3f3f;
struct G
{
int v, cap, next;
G() {}
G(int v, int cap, int next) : v(v), cap(cap), next(next) {}
} E[maxm];
int p[maxn], T;
int d[maxn], temp_p[maxn], qw[maxn]; //d顶点到源点的距离标号,temp_p当前狐优化,qw队列
void init()
{
memset(p, -1, sizeof(p));
T = 0;
}
void add(int u, int v, int cap)
{
E[T] = G(v, cap, p[u]);
p[u] = T++;
E[T] = G(u, 0, p[v]);
p[v] = T++;
}
bool bfs(int st, int en, int n)
{
int i, u, v, head, tail;
for(i = 0; i <= n; i++) d[i] = -1;
head = tail = 0;
d[st] = 0;
qw[tail] = st;
while(head <= tail)
{
u = qw[head++];
for(i = p[u]; i + 1; i = E[i].next)
{
v = E[i].v;
if(d[v] == -1 && E[i].cap > 0)
{
d[v] = d[u] + 1;
qw[++tail] = v;
}
}
}
return (d[en] != -1);
}
int dfs(int u, int en, int f)
{
if(u == en || f == 0) return f;
int flow = 0, temp;
for(; temp_p[u] + 1; temp_p[u] = E[temp_p[u]].next)
{
G& e = E[temp_p[u]];
if(d[u] + 1 == d[e.v])
{
temp = dfs(e.v, en, min(f, e.cap));
if(temp > 0)
{
e.cap -= temp;
E[temp_p[u] ^ 1].cap += temp;
flow += temp;
f -= temp;
if(f == 0) break;
}
}
}
return flow;
}
int dinic(int st, int en, int n)
{
int i, ans = 0;
while(bfs(st, en, n))
{
for(i = 0; i <= n; i++) temp_p[i] = p[i];
ans += dfs(st, en, inf);
}
return ans;
}
int n, m;
int main()
{
while(~scanf("%d %d", &n,&m)){
init();
int st = 2*n;
int en = st+1;
for(int i=1; i<=m; i++){
int u, v;
scanf("%d %d", &u,&v);
add(u,v+n,1);
}
for(int i=0; i<n; i++) add(st,i,1);
for(int i=n; i<2*n; i++) add(i,en,1);
int ans = dinic(st,en,en+1);
if(ans==n) puts("YES");
else puts("NO");
}
return 0;
}
E:
题意:有n个机器人(不超过4个),机器人都是要碰到障碍物或者机器人才会停下来的,不然就沿原方向一直走下去,4个机器人都可以移动,求最少移动多少步,机器人1可以到达出口(X)
解法:爆搜,BFS,题目中已经限定了步数,这就是可行性剪枝,难点在于如何表示矩形的状态,我们没有必要把矩形完全表示出来,直接把机器人拿出来即可,把位置拿出来做成一个int的Hash值,然后爆搜即可。队友写这个题,调试时间比较久,需要注意。
#include <bits/stdc++.h>
using namespace std;
int n, h, w, l, ex, ey;
const int dir[4][2] = {{0,1},{0,-1},{1,0},{-1,0}};
char g[20][20];
bool vis[100000000];
bool check(int x, int y){
if(x>=1&&x<=w&&y>=1&&y<=h&&g[x][y]=='.') return true;
else return false;
}
struct node{
int step;
int x[5], y[5];
int Hash(){
int ans = 0;
for(int i=1; i<=n; i++) ans = ans*10 + x[i];
for(int i=1; i<=n; i++) ans = ans*10 + y[i];
return ans;
}
}now;
void BFS()
{
now.step = 0;
queue <node >q;
q.push(now);
vis[now.Hash()] = 1;
while(q.size())
{
now = q.front();
q.pop();
if(now.step>l) continue;
if(now.x[1]==ex&&now.y[1]==ey){
printf("%d\n", now.step);
return;
}
node Next;
for(int i=1; i<=n; i++) g[now.x[i]][now.y[i]] = i+'0';
for(int i=1; i<=n; i++){
g[now.x[i]][now.y[i]] = '.';
for(int j=0; j<4; j++){
Next = now;
Next.step++;
while(check(Next.x[i]+dir[j][0], Next.y[i]+dir[j][1])){
Next.x[i]+=dir[j][0];
Next.y[i]+=dir[j][1];
}
if(!vis[Next.Hash()]){
vis[Next.Hash()] = 1;
q.push(Next);
}
}
g[now.x[i]][now.y[i]] = i + '0';
}
for(int i=1; i<=n; i++) g[now.x[i]][now.y[i]] = '.';//这里一定要改回来,因为x[i]和y[i]已经变化了
}
printf("NO SOLUTION\n");
}
int main()
{
scanf("%d %d %d %d", &n,&h,&w,&l);
for(int i=1; i<=w; i++){
scanf("%s", g[i]+1);
for(int j=1; j<=h; j++){
if(g[i][j]>='1'&&g[i][j]<='4'){
now.x[g[i][j]-'0'] = i;
now.y[g[i][j]-'0'] = j;
g[i][j] = '.';
}
else if(g[i][j] == 'X'){
ex = i;
ey = j;
g[i][j] = '.';
}
}
}
BFS();
return 0;
}
F:
题意:给出一些矩阵,如果矩阵之间有接触算作一个连通分量,问最大的连通分量的面积为多少?
解法:既然接触即算为一个连通分量,那么我们可以分别讨论他的横边接触和竖边接触。将横边和竖边分别存储在不同的结构体数组中,然后对数组进行排序,将互相接触的边对应的矩阵放在统一并查集中。最后统计下所有并查集的面积大小,输出最大的一个即可。
#include <bits/stdc++.h>
using namespace std;
const int maxn = 50010;
const int inf = 0x3f3f3f3f;
namespace DSU{
int fa[maxn];
void init(){
for(int i=1; i<maxn; i++) fa[i]=i;
}
int find_set(int x){
if(x==fa[x]) return x;
else return fa[x] = find_set(fa[x]);
}
void union_set(int x, int y){
x = find_set(x), y = find_set(y);
if(x!=y){
fa[x] = y;
}
}
}
using namespace DSU;
struct node{
int x, y, w, h;
}nodes[maxn];
struct line{
int x,l,r,pos;
bool operator<(const line &rhs) const{
return (x<rhs.x||x==rhs.x&&l<rhs.l||x==rhs.x&&l==rhs.l&&r<rhs.r);
}
}a[maxn*2], b[maxn*2];
//a存水平线段,b存垂直线段
int sum[maxn];
int main()
{
int n;
while(~scanf("%d", &n))
{
init();
for(int i=1; i<=n; i++){
scanf("%d %d %d %d", &nodes[i].x,&nodes[i].y,&nodes[i].w,&nodes[i].h);
//水平
a[i].x = nodes[i].y, a[i].l = nodes[i].x, a[i].r = nodes[i].x+nodes[i].w, a[i].pos = i;
a[i+n].x = nodes[i].y+nodes[i].h, a[i+n].l = nodes[i].x, a[i+n].r = nodes[i].x+nodes[i].w, a[i+n].pos = i;
//垂直
b[i].x = nodes[i].x, b[i].l = nodes[i].y, b[i].r = nodes[i].y+nodes[i].h, b[i].pos = i;
b[i+n].x = nodes[i].x + nodes[i].w, b[i+n].l = nodes[i].y, b[i+n].r = nodes[i].y+nodes[i].h, b[i+n].pos = i;
}
sort(a+1, a+2*n+1);
sort(b+1, b+2*n+1);
memset(sum, 0, sizeof(sum));
int m = 2*n;
//
for(int i=1,j; i<=m; ){
int tx = a[i].r;
j = i+1;
while(j<=m&&a[j].x==a[i].x&&a[j].l<=tx){
tx = max(tx, a[j].r);
union_set(a[i].pos, a[j].pos);
j++;
}
i=j;
}
//
for(int i=1,j; i<=m; ){
int tx = b[i].r;
j=i+1;
while(j<=m&&b[j].x==b[i].x&&b[j].l<=tx){
tx = max(tx, b[j].r);
union_set(b[i].pos, b[j].pos);
j++;
}
i=j;
}
for(int i=1; i<=n; i++){
sum[find_set(i)] += nodes[i].h*nodes[i].w;
}
int ans = 0;
for(int i=1; i<=n; i++){
ans = max(ans, sum[i]);
}
printf("%d\n", ans);
}
return 0;
}
G:
题意:给出两个图,通过缩小,问两个图是否是相同的,缩小的时候不是按比例,比如说两行间距为三可以缩小为二,为6可以缩小为5
解法:读这个题的时候我就知道是矩阵离散化了,然后写完离散化丢给队友写暴力旋转4个方向判相等,除了离散化开始写多了点东西,基本没什么问题。
#include <bits/stdc++.h>
using namespace std;
const int maxn = 3010;
int W1, H1, N;
int W2, H2;
int X1[maxn], X2[maxn], Y1[maxn], Y2[maxn];
int X3[maxn], Y3[maxn], X4[maxn], Y4[maxn];
void Rotate()
{
int ymax = *max_element(Y1, Y1+N);
for(int i=0; i<N; i++){
int x = ymax - Y1[i];
int y = X1[i];
X1[i] = x;
Y1[i] = y;
}
}
bool check()
{
//第一个离散化后矩形的最左下角的值
int p1 = 0;
for(int i=1; i<N; i++){
if(make_pair(X1[i], Y1[i]) < make_pair(X1[p1], Y1[p1])){
p1 = i;
}
}
//第一个离散化后矩形的最左下角的值
int p2 = 0;
for(int i=1; i<N; i++){
if(make_pair(X3[i],Y3[i]) < make_pair(X3[p2], Y3[p2])){
p2 = i;
}
}
for(int i=0; i<N; i++){
int q1 = (p1+i)%N;
int q2 = (p2+i)%N;
if(X1[q1] != X3[q2] || Y1[q1] != Y3[q2]) return false;
}
return true;
}
//对x1和x2进行坐标离散化,并且返回离散化后的宽度
int compress(int *x1, int *x2, int w)
{
vector <int> xs(x2, x2 + N);
sort(xs.begin(), xs.end());
xs.erase(unique(xs.begin(), xs.end()), xs.end());
for(int i = 0; i < N; i++)
{
x1[i] = lower_bound(xs.begin(), xs.end(), x1[i]) - xs.begin();
x2[i] = lower_bound(xs.begin(), xs.end(), x2[i]) - xs.begin();
}
return xs.size();
}
int main()
{
while(~scanf("%d", &N))
{
//ZXY
W1 = 0;
H1 = 0;
for(int i = 0; i < N; i++)
{
scanf("%d %d", &X1[i], &Y1[i]);
X2[i] = X1[i], Y2[i] = Y1[i];
W1 = max(W1, X1[i]);
H1 = max(H1, Y1[i]);
}
W1 = compress(X1, X2, W1);
H1 = compress(Y1, Y2, H1);
W2 = 0;
H2 = 0;
scanf("%d", &N);
for(int i = 0; i < N; i++)
{
scanf("%d %d", &X3[i], &Y3[i]);
X4[i] = X3[i], Y4[i] = Y3[i];
W2 = max(W2, X3[i]);
H2 = max(H2, Y3[i]);
}
W2 = compress(X3, X4, W2);
H2 = compress(Y3, Y4, H2);
//LYY
if(check())
{
puts("yes");
continue;
}
Rotate();
if(check())
{
puts("yes");
continue;
}
Rotate();
if(check())
{
puts("yes");
continue;
}
Rotate();
if(check())
{
puts("yes");
continue;
}
puts("no");
}
return 0;
}
H: 留坑
I:给了一些数字和字符,字符里面有问号,这个序列是个圆形,我们可以从任意一个数字开始重新启动一个序列,现在序列里面每个?可以替换成+,-,*任意一个,且优先级随便考虑,现在问这n次移动构成的n个序列的最小值和最大值是多少,输出每一轮的最小最大的绝对值,拼成一个字符串,最后输出一个空行。n<=200。
解法:把字符串copy一份,变成长度400的字符串,对这个序列做一个区间DP即可。转移很简单,看代码吧。
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<cstring>
using namespace std;
typedef long long LL;
const int maxn = 405;
LL mx[maxn][maxn];
LL mi[maxn][maxn];
void getMaxMin(const vector<int> &num, const vector<char> &symbol)
{
memset(mx, 0x80, sizeof(mx));
memset(mi, 0x7F, sizeof(mi));
int n = num.size();
for(int i = 0; i < n; i++)
{
mi[i][i] = mx[i][i] = num[i];
}
for(int len = 2; len <= n / 2; len++)
{
for(int st = 0; st + len <= n; st++)
{
int en = st + len - 1;
for(int mid = st; mid < en; mid++)
{
if(symbol[mid] == '+' || symbol[mid] == '?')
{
mi[st][en] = min(mi[st][en], mi[st][mid] + mi[mid + 1][en]);
mx[st][en] = max(mx[st][en], mx[st][mid] + mx[mid + 1][en]);
}
if(symbol[mid] == '-' || symbol[mid] == '?')
{
mi[st][en] = min(mi[st][en], mi[st][mid] - mx[mid + 1][en]);
mx[st][en] = max(mx[st][en], mx[st][mid] - mi[mid + 1][en]);
}
if(symbol[mid] == '*' || symbol[mid] == '?')
{
mi[st][en] = min(mi[st][en], mi[st][mid] * mi[mid + 1][en]);
mi[st][en] = min(mi[st][en], mi[st][mid] * mx[mid + 1][en]);
mi[st][en] = min(mi[st][en], mx[st][mid] * mi[mid + 1][en]);
mi[st][en] = min(mi[st][en], mx[st][mid] * mx[mid + 1][en]);
mx[st][en] = max(mx[st][en], mx[st][mid] * mx[mid + 1][en]);
mx[st][en] = max(mx[st][en], mx[st][mid] * mi[mid + 1][en]);
mx[st][en] = max(mx[st][en], mi[st][mid] * mx[mid + 1][en]);
mx[st][en] = max(mx[st][en], mi[st][mid] * mi[mid + 1][en]);
}
}
}
}
}
int main()
{
int n;
while(~scanf("%d", &n))
{
vector<int> num;
vector<char> symbol;
for(int i = 0; i < n; i++)
{
int x;
scanf("%d", &x);
num.push_back(x);
char s[10];
scanf("%s", s);
symbol.push_back(s[0]);
}
for(int i = 0; i < n; i++)
{
num.push_back(num[i]);
symbol.push_back(symbol[i]);
}
getMaxMin(num, symbol);
for(int i = 0; i < n; i++)
{
printf("%lld%lld", abs(mi[i][i + n - 1]), abs(mx[i][i + n - 1]));
}
printf("\n");
}
return 0;
}
J:
题意:给一个小矩形,再给一个大矩形,问小矩形在大矩形出现的次数。
解法:矩阵Hash。双值Hash,模数取成121和131即可过。
#include <bits/stdc++.h>
using namespace std;
const unsigned int BASE1 = 121;
const unsigned int BASE2 = 131;
const int mod = 99999997;
const int maxn = 2010;
int n, m, a, b, q;
unsigned int hash1[maxn][maxn], hash2[maxn][maxn];
unsigned int bas1[maxn], bas2[maxn];
unsigned int getHash()
{
for (int i = 1; i <= a; i++)
for (int j = 1; j <= b; j++)
hash2[i][j] += hash2[i - 1][j] * BASE1;
for (int i = 1; i <= a; i++)
{
for (int j = 1; j <= b; j++)
{
hash2[i][j] += hash2[i][j - 1] * BASE2;
}
}
return hash2[a][b];
}
char str1[maxn];
int main()
{
while (~scanf("%d %d %d %d", &a, &b, &n, &m))
{
for (int i = 1; i <= a; i++)
{
scanf("%s", str1+1);
for (int j = 1; j <= b; j++)
{
if (str1[j] == 'o')
{
hash2[i][j] = 1;
}
else
{
hash2[i][j] = 0;
}
}
}
unsigned int des = getHash();
bas1[0] = bas2[0] = 1;
for (int i = 1; i <= 1010; i++) bas1[i] = bas1[i - 1] * BASE1, bas2[i] = bas2[i - 1] * BASE2;
for (int i = 1; i <= n; i++)
{
scanf("%s", str1+1);
for (int j = 1; j <= m; j++)
{
if (str1[j] == 'o')
{
hash1[i][j] = 1;
}
else
{
hash1[i][j] = 0;
}
}
}
int ans = 0;
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= m; j++)
{
hash1[i][j] += hash1[i - 1][j] * BASE1;
}
}
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= m; j++)
{
hash1[i][j] += hash1[i][j - 1] * BASE2;
}
}
for(int i=a; i<=n; i++){
for(int j=b; j<=m; j++){
unsigned int h = hash1[i][j];
h -= hash1[i-a][j]*bas1[a];
h -= hash1[i][j-b]*bas2[b];
h += hash1[i-a][j-b]*bas1[a]*bas2[b];
if(h==des){
ans++;
}
}
}
printf("%d\n", ans);
}
return 0;
}