A. 机器人足球
模拟
#include <iostream>
#include <cmath>
#include <cstdio>
using namespace std;
double dis(int x1, int y1, int x2, int y2) {
return sqrt((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2));
}
int main() {
int x, y;
scanf("%d%d", &x, &y);
double d = dis(x,y,100,10)-10;
printf("%.3lf\n",max(d,0.));
}
B.纸牌识别
模拟
#include <iostream>
#include <cstdio>
#include <set>
using namespace std;
typedef pair<int,int> pii;
char x;
int y;
int a[200];
set<pii> s;
int main() {
a['P']=a['K']=a['H']=a['T']=13;
while (~scanf(" %c%d", &x, &y)) {
if (s.count(pii(x,y))) return puts("GRESKA"),0;
s.insert(pii(x,y));
--a[x];
}
printf("%d %d %d %d\n",a['P'],a['K'],a['H'],a['T']);
}
C.卡牌对决
贪心, 前$\frac{n}{2}$轮尽量取最大, 后$\frac{n}{2}$轮尽量取最小.
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <set>
#define REP(i,a,n) for(int i=a;i<=n;++i)
using namespace std;
const int N = 1e5+10;
int n, a[N];
set<int> Bob, Alice;
int main() {
scanf("%d", &n);
REP(i,1,n) {
scanf("%d",a+i);
Bob.insert(a[i]);
}
REP(i,1,2*n) if (!Bob.count(i)) Alice.insert(i);
int ans = 0;
sort(a+1,a+1+n/2,greater<int>());
REP(i,1,n/2) {
int x = *(--Alice.end());
if (x>a[i]) ++ans, Alice.erase(x);
}
sort(a+1+n/2,a+1+n);
REP(i,n/2+1,n) {
int x = *Alice.begin();
if (x<a[i]) ++ans, Alice.erase(x);
}
printf("%d\n", ans);
}
D.自驾游
先跑$2$次$dijkstra$求出$N$到每个点最短路, 再建图跑一次$dijkstra$求出$1$到$N$的最短路即为最少花费.
#include <iostream>
#include <cstdio>
#include <queue>
#include <vector>
#include <string.h>
#define REP(i,a,n) for(int i=a;i<=n;++i)
#define pb push_back
using namespace std;
typedef long long ll;
const int N = 5e4+10;
int n, m;
struct _ {
int to,w;
_ (int to=0,int w=0) :to(to),w(w) {}
};
vector<_> g1[N], g2[N], g3[N];
ll d1[N], d2[N], d3[N];
int vis[N], u[N], v[N], p[N], q[N];
struct node {
int id;
ll w;
node (int id=0, ll w=0) :id(id),w(w) {}
bool operator < (const node &rhs) const {
return w>rhs.w;
}
};
priority_queue<node> pq;
void dij(vector<_> g[], ll d[], int s) {
memset(d,0x3f,sizeof d1);
memset(vis,0,sizeof vis);
pq.push(node(s,d[s]=0));
while (pq.size()) {
int u = pq.top().id; pq.pop();
if (vis[u]) continue;
vis[u] = 1;
for (int i=0; i<g[u].size(); ++i) {
_ e = g[u][i];
ll dd = e.w+d[u];
if (dd<d[e.to]) pq.push(node(e.to,d[e.to]=dd));
}
}
}
int main() {
scanf("%d%d", &n, &m);
REP(i,1,m) {
scanf("%d%d%d%d", u+i, v+i, p+i, q+i);
g1[v[i]].pb(_(u[i],p[i]));
g2[v[i]].pb(_(u[i],q[i]));
}
dij(g1,d1,n);
dij(g2,d2,n);
REP(i,1,m) {
int c = 0;
if (d1[v[i]]+p[i]>d1[u[i]]) ++c;
if (d2[v[i]]+q[i]>d2[u[i]]) ++c;
g3[u[i]].pb(_(v[i],c));
}
dij(g3,d3,1);
printf("%lld\n", d3[n]);
}
E.现代艺术
对称轴有两种情况, (1)点$1$与其他点的中垂线. (2)过点$1$.
情况(1)直接枚举, 对于情况(2)我们再考虑点$2$的位置, $2$要么也在对称轴上, 否则对称轴一定在$2$与其他点连线的中垂线上, 再对点$2$判断一次即可.
F.割草
刚开始以为每次修改一个连通块会最优, 但下面这组数据最优情况是修改$(1,1),(2,2),(5,1)$最优解为$61$, 若删连通块的话最优只能取到$63$
5 4 5 7
#...
.#..
.###
.###
#...
然后说正解, $S$连低点, 容量为$A$, 高点连$T$容量为$A$, 每个点$(i,j)$再向$(i+1,j)$和$(i,j+1)$连边, 若同为高点或低点, 连双向边, 容量为$B$, 否则低点向高点连边, 容量为$B$. 跑最小割即为答案.
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <queue>
#define REP(i,a,n) for(int i=a;i<=n;++i)
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
const int N = 1e6+10, INF = 0x3f3f3f3f;
int n, m, a, b, S, T;
char s[55][55];
struct edge {
int v,next;
ll w;
edge () {}
edge (int v,int next,ll w) :v(v),w(w),next(next) {}
} e[N];
int head[N], dep[N], vis[N], cur[N], cnt=1;
queue<int> Q;
pii get(int t) {
int x = (t-1)/m+1, y = (t-1)%m+1;
return pii(x,y);
}
void add(int u, int v, ll w) {
e[++cnt] = edge(v,head[u],w);
head[u] = cnt;
e[++cnt] = edge(u,head[v],0);
head[v] = cnt;
}
int bfs() {
REP(i,1,T) dep[i]=INF,vis[i]=0,cur[i]=head[i];
dep[S]=0,Q.push(S);
while (Q.size()) {
int u = Q.front(); Q.pop();
for (int i=head[u]; i; i=e[i].next) {
if (dep[e[i].v]>dep[u]+1&&e[i].w) {
dep[e[i].v]=dep[u]+1;
Q.push(e[i].v);
}
}
}
return dep[T]!=INF;
}
ll dfs(int x, ll w) {
if (x==T) return w;
ll used = 0;
for (int i=cur[x]; i; i=e[i].next) {
cur[x] = i;
if (dep[e[i].v]==dep[x]+1&&e[i].w) {
int flow = dfs(e[i].v,min(w-used,e[i].w));
if (flow) {
used += flow;
e[i].w -= flow;
e[i^1].w += flow;
if (used==w) break;
}
}
}
return used;
}
ll dinic() {
ll ans = 0;
while (bfs()) ans+=dfs(S,1e18);
return ans;
}
int id(int x, int y) {
return (x-1)*m+y;
}
int main() {
scanf("%d%d%d%d", &n, &m, &a, &b);
REP(i,1,n) scanf("%s",s[i]+1);
S = n*m+1, T = S+1;
REP(i,1,n) REP(j,1,m) {
if (s[i][j]=='.') add(S,id(i,j),b);
else add(id(i,j),T,b);
if (i!=n) {
if (s[i][j]=='.'&&s[i+1][j]=='#') add(id(i,j),id(i+1,j),a);
else add(id(i+1,j),id(i,j),a);
if (s[i][j]==s[i+1][j]) add(id(i,j),id(i+1,j),a);
}
if (j!=m) {
if (s[i][j]=='.'&&s[i][j+1]=='#') add(id(i,j),id(i,j+1),a);
else add(id(i,j+1),id(i,j),a);
if (s[i][j]==s[i][j+1]) add(id(i,j),id(i,j+1),a);
}
}
printf("%lld\n", dinic());
}
G.括号序列
$dp$求出长为$x$, 左括号比右括号多$y$个时的方案数. 然后从前往后枚举, 若放'('的方案数不少于$k$则放'(', 否则放')'.
注意方案数是指数级, 会爆long long.
#include <iostream>
#define REP(i,a,b) for(int i=a;i<=b;++i)
using namespace std;
typedef long long ll;
const int N = 2010;
int n;
ll f[N][N], k;
char ans[N];
int main() {
scanf("%d%lld", &n, &k);
f[0][0] = 1;
ll INF = 1e18+10;
REP(i,1,n) REP(j,0,n) if (f[i-1][j]) {
f[i][j+1] += f[i-1][j];
if (j) f[i][j-1] += f[i-1][j];
f[i][j+1] = min(f[i][j+1], INF);
if (j) f[i][j-1] = min(f[i][j-1], INF);
}
int now = 0;
REP(i,1,n) {
if (f[n-i][now+1]>=k) ans[i]='(',++now;
else ans[i]=')',k-=f[n-i][now+1],--now;
}
puts(ans+1);
}
H. 不要回文
大回文一定包含小回文, 只用判断长为2和3的回文即可.
#include <iostream>
#include <cstdio>
#include <string.h>
#define REP(i,a,n) for(int i=a;i<=n;++i)
using namespace std;
const int N = 1e6+10;
int n, a[N];
char s[N];
int main() {
scanf("%s", s+1);
n = strlen(s+1);
REP(i,1,n) a[i]=s[i];
int ans = 0, cur = 0;
REP(i,1,n) {
if (a[i]==a[i-1]) ++ans,a[i]=--cur;
if (i>=2&&a[i]==a[i-2]) ++ans,a[i]=--cur;
}
printf("%d\n", ans);
}
I. 你的名字
判断一个串是否是另一个串的子序列, 二分$O(nlogn)$, 或者序列自动机$O(n\Sigma)$.
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <string.h>
#include <vector>
#define REP(i,a,n) for(int i=a;i<=n;++i)
#define pb push_back
using namespace std;
const int N = 1e3+10;
int n, m;
char s[N][N], p[N][N];
vector<int> g[N];
int chk(char *p) {
int n = strlen(p), now = 0;
REP(i,0,n-1) {
vector<int>::iterator t = lower_bound(g[p[i]].begin(),g[p[i]].end(),now);
if (t==g[p[i]].end()) return 0;
now = *t+1;
}
return 1;
}
void trans(char *s) {
int n = strlen(s);
REP(i,0,n-1) if (islower(s[i])) s[i]=s[i]-'a'+'A';
}
int main() {
scanf("%d%d", &n, &m);
REP(i,1,n) scanf("%s",s[i]),trans(s[i]);
REP(i,1,m) scanf("%s",p[i]),trans(p[i]);
REP(i,1,n) {
int ans = 0, n = strlen(s[i]);
REP(j,0,n-1) g[s[i][j]].pb(j);
REP(j,1,m) ans += chk(p[j]);
REP(j,0,n-1) g[s[i][j]].clear();
printf("%d\n", ans);
}
}
J.密信
$dp[i][j]$为前$i$位匹配$j$位的方案数, 直接算是$O(|p|n)$, 矩阵优化到$O(|p|^3logn)$.
#include <iostream>
#include <cstdio>
#include <string.h>
#define REP(i,a,n) for(int i=a;i<=n;++i)
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int N = 55;
ll n, M;
int len, f[N];
char s[N];
void getFail() {
int j = 0;
REP(i,1,len-1) {
while (j&&s[j]!=s[i]) j=f[j];
if (s[j]==s[i]) ++j;
f[i+1] = j;
}
}
ull qmul(const ull a, const ull b, const ull md) {
ll c=(ll)a*b-(ll)((ull)((long double)a*b/md)*md);
return c<0?md+c:((ull)c>md?c-md:c);
}
struct Mat {
ll v[55][55];
Mat() {memset(v, 0, sizeof v);}
Mat operator * (const Mat& b) const {
Mat c;
REP(k,0,len)REP(i,0,len)REP(j,0,len) {
c.v[i][j] = (qmul(v[i][k],b.v[k][j],M)+c.v[i][j])%M;
}
return c;
}
Mat operator ^ (ll nn) {
Mat b, a=*this;
REP(i,0,len) b.v[i][i]=1;
while(nn) {
if(nn&1LL) b=b*a;
nn>>=1LL,a=a*a;
}
return b;
}
};
int main() {
int t;
scanf("%d", &t);
while (t--) {
scanf("%lld%lld%s", &n, &M, s);
len = strlen(s);
getFail();
Mat g;
REP(i,0,len) REP(k,'a','z') {
int nxt = i;
while (nxt&&s[nxt]!=k) nxt = f[nxt];
if (s[nxt]==k) ++nxt;
if (i==len) nxt = len;
++g.v[nxt][i];
}
g = g^n;
printf("%lld\n", g.v[len][0]);
}
}
K.福报
点$i$的贡献为$i$的子树内$r_j<r_i$的$t_j$的和.
比赛的时候是用线段树合并写的, 实际上预处理出$dfs$序后, 贡献明显是个二维数点, 按$r$排序后, 树状数组统计一下即可, 这样时空的常数都会小很多.
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <vector>
#define REP(i,a,n) for(int i=a;i<=n;++i)
#define pb push_back
using namespace std;
typedef long long ll;
const int N = 1e6+10;
int n, clk, rt;
int fa[N], L[N], R[N];
ll c[N], ans[N];
vector<int> g[N];
struct _ {int r,t,id;} a[N];
bool cmp(_ a, _ b) {
return a.r<b.r;
}
void dfs(int x) {
L[x] = ++clk;
for (int i=0; i<g[x].size(); ++i) dfs(g[x][i]);
R[x] = clk;
}
void add(int x, int v) {
for (; x<=n; x+=x&-x) c[x]+=v;
}
ll qry(int x) {
ll r = 0;
for (; x; x^=x&-x) r+=c[x];
return r;
}
int main() {
scanf("%d", &n);
REP(i,1,n) {
scanf("%d%d%d",fa+i,&a[i].r,&a[i].t);
a[i].id = i;
if (fa[i]==-1) rt = i;
else g[fa[i]].pb(i);
}
dfs(rt);
sort(a+1,a+1+n,cmp);
REP(i,1,n) {
int j = i;
while (a[j+1].r==a[i].r) ++j;
REP(k,i,j) ans[a[k].id] = qry(R[a[k].id])-qry(L[a[k].id]-1);
REP(k,i,j) add(L[a[k].id],a[k].t);
i = j;
}
REP(i,1,n) printf("%lld\n", ans[i]);
}
L. 曲奇工厂
暴力枚举所有情况, 复杂度$O(n!2^n)$.
#include <iostream>
#include <cstdio>
#include <algorithm>
#define REP(i,a,n) for(int i=a;i<=n;++i)
using namespace std;
const int N = 10;
int n, c, s, cnt;
int f[N];
struct {int x,y;} a[N], b[N], d[N];
int ans;
int calc(int n) {
int sum = 0, v = s, ans = 0;
REP(i,1,n) {
if (sum<d[i].x) {
int t = (d[i].x-sum+v-1)/v;
sum += t*v;
ans += t;
}
sum -= d[i].x;
v += d[i].y;
}
if (sum<c) ans += (c-sum+v-1)/v;
return ans;
}
void dfs(int now) {
if (now>n) {
REP(i,1,cnt) f[i] = i;
do {
REP(i,1,cnt) d[i]=b[f[i]];
ans = min(ans, calc(cnt));
} while (next_permutation(f+1,f+1+cnt));
}
else {
dfs(now+1);
b[++cnt] = a[now];
dfs(now+1);
--cnt;
}
}
int main() {
scanf("%d%d%d", &n, &c, &s);
REP(i,1,n) scanf("%d%d",&a[i].x,&a[i].y);
ans = (c+s-1)/s;
dfs(1);
printf("%d\n", ans);
}