B - Alternative Bracket Notation
给出了一种表示括号匹配的方法,要求对于给定的括号序列求出长度最小的表达。
通过观察这种方法可以发现,它的基本实现过程仍然是不断地把左括号扔进栈里,然后每次遇到右括号的时候取出来。一对括号在表达式中占的长度分为三部分,左右端点的数字长度以及2个字符的特殊字符。
用la[i]和lb[i]表示第i个位置上的括号,它左端点和右端点的数字长度。初始的时候显然都为1,然后在将左括号扔进去的时候,左端点的值应该是当前的序列长度+两个数字的长度+2,并更新当前序列长度。
这样处理完一遍后,会出现一个问题,就是一个位置上的数的长度增加以后,会对前面的某些数产生影响。这时候就对每对括号的左右端点,检查端点数字的实际长度与记录的端点数字长度是否相同。重复计算直到所有位置数字都合法为止。
#include<cstdio>
#include<algorithm>
#include<cmath>
#include <cstring>
#include <iostream>
#include<map>
using namespace std;
const int maxn = 4050;
int n, numl[maxn*15];
int la[maxn], lb[maxn];
int sta[maxn], L[maxn], R[maxn];
char s[maxn];
void init()
{
numl[0] = 0;
for(int i = 1;i < maxn*15;i++)
numl[i] = numl[i/10] + 1;
}
int main()
{
init();
scanf("%s", s);
n = strlen(s);
for(int i = 0;i < n;i++) la[i] = lb[i] = 1;
while(1)
{
int top = 0, cur = 0;
for(int i = 0;i < n;i++)
{
if(s[i] == '(')
{
cur += la[i] + lb[i] + 2;
L[i] = cur;
sta[top++] = i;
}
else R[sta[--top]] = cur;
}
bool flag = 1;
for(int i = 0;i < n;i++)
{
if(s[i] == '(')
{
if(numl[L[i]] != la[i] || lb[i] != numl[R[i]])
{
flag = 0;
break;
}
}
}
if(flag) break;
for(int i = 0;i < n;i++)
{
if(s[i] == '(')
la[i] = numl[L[i]], lb[i] = numl[R[i]];
}
}
for(int i = 0;i < n;i++)
{
if(s[i] == '(')
printf("%d,%d:", L[i], R[i]);
}
printf("\n");
return 0;
}
C - Greetings!
有n种大小不同的矩形,现在需要选择k种任意大小的矩形,它们全部覆盖住,使得浪费的面积最小。
1<=n,k<=15,一开始想搜索,然后就TLE了……
正解是状压dp,dp[i][S]表示用i种矩形覆盖S这个集合浪费的最小面积。
for(int k = p;k;k = (k-1)&p) 这个操作可以枚举二进制p的所有子集,枚举所有方案的所有子集的时间复杂度是。学到了Orz
#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
typedef long long ll;
const int maxn = 16;
const ll INF = (1LL << 62) - 1;
int n, k;
ll dp[maxn][1<<maxn], val[1<<maxn], sum[1<<maxn];
int H[1<<maxn], W[1<<maxn], num[1<<maxn];
struct node
{
int h, w;
int cnt;
}e[maxn];
int main()
{
scanf("%d%d", &n, &k);
for(int i = 0;i < n;i++)
{
scanf("%d%d%d", &e[i].w, &e[i].h, &e[i].cnt);
}
memset(sum, 0, sizeof(sum));
for(int i = 0;i < (1<<n);i++)
{
for(int j = 0;j < n;j++)
{
if((i>>j) & 1)
{
H[i] = max(H[i], e[j].h);
W[i] = max(W[i], e[j].w);
sum[i] += 1LL*e[j].h*e[j].w*e[j].cnt;
num[i] += e[j].cnt;
}
}
val[i] = 1LL*H[i]*W[i]*num[i] - sum[i];
}
for(int i = 0;i <= k;i++)
{
for(int j = 0;j < (1<<n);j++)
dp[i][j] = INF;
}
dp[0][0] = 0;
for(int i = 1;i <= k;i++)
{
for(int j = 0;j < (1<<n);j++)
{
ll tmp = INF;
for(int p = j;p;p = (p-1) & j)
{
if(dp[i-1][j-p] < INF)
tmp = min(tmp, dp[i-1][j-p] + val[p]);
}
dp[i][j] = tmp;
}
}
ll ans = INF;
for(int i = 0;i <= k;i++)
ans = min(ans, dp[i][(1<<n)-1]);
printf("%I64d\n", ans);
return 0;
}
D - Programming Team
树上的每个点都有两个参数p和s,在树上选取k+1个连通的点且包含根节点,使得sum(p)/sum(s)最大。
01分数规划和树形依赖背包组合起来的裸题。
当不做树上的限制的时候,就是一个01分数规划。处理的方法是二分这个比值x,将其按照p - x*s排序然后贪心地去取。
当不做比值限制的时候,就是一个树形依赖背包。首先处理出dfs序以及以每个点为根的子树的大小。因为只有选了某个点,才能选它的子节点。因此在到每个点的时候两种情况,一种是选这个点,然后跳到下一个点;另一种是不选这个点,然后跳过整个子树。
将这两个东西搞到一起就可以解决本题。时间复杂度O(nklogn)。
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
#include <cmath>
#include <vector>
using namespace std;
typedef long long ll;
const int maxn = 2505;
const ll INF = (1LL << 62) - 1;
int n, k, x, no, en[maxn], pos[maxn], num[maxn];
double s[maxn], p[maxn], dp[maxn][maxn], val[maxn];
vector<int>maze[maxn];
void dfs(int u)
{
num[u] = ++no;
pos[no] = u;
int len = maze[u].size();
for(int i = 0;i < len;i++)
{
int v = maze[u][i];
dfs(v);
}
en[u] = no;
}
bool judge(double x)
{
for(int i = 1;i <= n;i++)
val[i] = p[i] - x*s[i];
for(int i = 0;i <= n+1;i++)
{
for(int j = 0;j <= k;j++)
dp[i][j] = -INF;
}
dp[1][0] = 0;
for(int i = 0;i <= n;i++)
{
for(int j = 0;j <= k;j++)
{
if(dp[i][j] <= -INF) continue;
if(j < k) dp[i+1][j+1] = max(dp[i+1][j+1], dp[i][j] + val[pos[i]]);
dp[en[pos[i]]+1][j] = max(dp[en[pos[i]]+1][j], dp[i][j]);
}
}
return (dp[n+1][k] > 0);
}
int main()
{
scanf("%d%d", &k, &n);
for(int i = 1;i <= n;i++)
{
scanf("%lf%lf%d", &s[i], &p[i], &x);
maze[x].push_back(i);
}
no = 0;
for(int i = 1;i <= n;i++)
{
if(!num[i])
dfs(i);
}
double L = 0, R = 10005, mid;
for(int i = 0;i < 50;i++)
{
mid = (L + R)/2;
if(judge(mid)) L = mid;
else R = mid;
}
printf("%.3lf\n", L);
return 0;
}
E - K-Inversions
给出一个只含有A和B的字符串,定义如果si为B,sj为A, 且j-i=k,这就产生了一个k-Inversion。求1~n-1的Inversion数目。
考虑当j > i时,也就是说,j + (n-i) > n。构造两个多项式,A[i] = (s[i] == 'B'),B[n-i-1] = (s[i] == 'A'),FFT加速卷积取n~n*2-1项的系数即可。
#include <cstdio>
#include <cstring>
#include <cmath>
#include <vector>
#include <iostream>
#include <algorithm>
typedef long long ll;
using namespace std;
const ll INF = (1LL << 62) - 1;
const double pi = acos(-1.0);
const int maxn = 6000050;
int n, len, ans[maxn];
char s[maxn];
struct Complex
{
double x, y;
Complex (double _x = 0.0, double _y = 0.0)
{
x = _x;
y = _y;
}
Complex operator - (const Complex &o) const
{
return Complex(x - o.x, y - o.y);
}
Complex operator + (const Complex &o) const
{
return Complex(x + o.x, y + o.y);
}
Complex operator * (const Complex &o) const
{
return Complex(x*o.x - y*o.y, x*o.y + y*o.x);
}
}a[maxn], b[maxn];
void change(Complex y[], int len)
{
int i, j, k;
for(i = 1, j = len/2;i < len-1;i++)
{
if(i < j) swap(y[i], y[j]);
k = len/2;
while(j >= k)
{
j -= k;
k /= 2;
}
if(j < k) j += k;
}
}
void FFT(Complex y[], int len, int op)
{
change(y, len);
for(int h = 2;h <= len;h <<= 1)
{
Complex wn(cos(-op*2*pi/h), sin(-op*2*pi/h));
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;
}
}
}
}
int main()
{
scanf("%s", s);
n = strlen(s);
len = 1;
while(len < n*2) len <<= 1;
for(int i = 0;i < n;i++)
{
if(s[i] == 'A')
{
a[n-i-1] = Complex(0, 0);
b[i] = Complex(1, 0);
}
else
{
a[n-i-1] = Complex(1, 0);
b[i] = Complex(0, 0);
}
}
for(int i = n;i < len;i++)
a[i] = b[i] = Complex(0, 0);
FFT(a, len, 1);
FFT(b, len, 1);
for(int i = 0;i < len;i++)
a[i] = a[i]*b[i];
FFT(a, len, -1);
for(int i = 0;i < len;i++)
ans[i] = (int)(a[i].x/len + 0.5);
for(int i = 0;i < n - 1;i++)
printf("%d\n", ans[i + n]);
return 0;
}
F - Mountain Scenes
将n个1*1方块堆到h*w格子里,问有多少种不同的出现山峰的方案。
dp[i][j]表示前i列放了j个的方案数,dp[i][j+k] = Sum{dp[i-1][j]}转移,求出所有的方案数,最后减去没有山峰的种情况即可。
#include <cstdio>
#include <cstring>
#include <cmath>
#include <vector>
#include <iostream>
#include <algorithm>
typedef long long ll;
using namespace std;
const ll INF = (1LL << 62) - 1;
const ll mod = 1e9 + 7;
int n, w, h;
ll dp[105][10005];
int main()
{
scanf("%d%d%d", &n, &w, &h);
memset(dp, 0, sizeof(dp));
dp[0][0] = 1;
for(int i = 1;i <= w;i++)
{
for(int j = 0;j <= n;j++)
{
if(dp[i-1][j] == 0) continue;
for(int k = 0;k <= h;k++)
{
if(j + k > n) break;
dp[i][j+k] += dp[i-1][j];
dp[i][j+k] %= mod;
}
}
}
ll ans = 0;
for(int i = 0;i <= n;i++)
ans = (ans + dp[w][i]) % mod;
for(int i = 0;i <= h;i++)
{
if(i*w <= n)
ans--;
}
ans = (ans + mod) % mod;
printf("%I64d\n", ans);
return 0;
}
G - Symmetry
给出n(n<=1000)个点构成的集合,要求向集合中添加一些点,使得存在一个点或一条直线,点集中每个点关于它的对称点也在这个点集中。求添加的最小的数目。
考虑枚举所有的对称点和对称直线。首先我们将所有的坐标都乘以2,避免小数的出现。对于一个点,可以直接hash成一个整数扔进map里。对于一条直线,需要通过斜率和截距来保存。斜率用和来表示,而截距,可以储存,它一定是一个整数。对于这三个整数,为了去重,我们将其除以三者的gcd,这样可以保证同一直线上,三个参数必然相同。这三个数可以用结构体存也可以hash掉然后扔进map里。
最后大力优化一下常数,全程不要使用double,全部用long long实现。
#include <cstdio>
#include <algorithm>
#include <map>
using namespace std;
typedef long long ll;
const int maxn = 1005;
ll gcd(ll a, ll b)
{
if(a < 0) return gcd(-a, b);
if(b < 0) return gcd(a, -b);
if(b == 0) return a;
return gcd(b, a % b);
}
struct Point
{
ll x, y;
Point(ll _x = 0, ll _y = 0)
{
x = _x; y = _y;
}
Point operator + (const Point &b)const
{
return Point(x+b.x, y+b.y);
}
Point operator / (const ll &b)const
{
return Point(x/b, y/b);
}
}p[maxn];
struct Line
{
ll a, b, c;
void init()
{
if(a < 0 || (a == 0 && b < 0))
a *= -1, b *= -1, c *= -1;
ll g = gcd(gcd(a, b), c);
a /= g, b /= g, c /= g;
}
bool online(Point p)
{
ll res = a*p.x;
res += b*p.y;
res += c;
return (res == 0);
}
bool operator == (const Line &o) const
{
return a == o.a && b == o.b && c == o.c;
}
bool operator < (const Line &o) const
{
if(a != o.a) return a < o.a;
if(b != o.b) return b < o.b;
return c < o.c;
}
};
map<ll, int>vis, mp;
map<ll, int>::iterator it;
map<Line, int>tp;
map<Line, int>::iterator itt;
Line Connect(Point p1, Point p2)
{
Line res;
res.a = p1.y - p2.y;
res.b = p2.x - p1.x;
res.c = -(res.a*p1.x + res.b*p1.y);
res.init();
return res;
}
Line Vertline(Point p1, Point p2)
{
Line res;
Point mid = (p1 + p2)/2;
res.a = p2.x - p1.x;
res.b = p2.y - p1.y;
res.c = -(res.a*mid.x + res.b*mid.y);
res.init();
return res;
}
int n;
int main()
{
scanf("%d", &n);
for(int i = 0; i < n; i++)
{
scanf("%lld%lld", &p[i].x, &p[i].y);
p[i].x = 2*(p[i].x + 20000), p[i].y = 2*(p[i].y + 20000);
vis[p[i].x*100000 + p[i].y]= 1;
}
int ans = n-1;
for(int i = 0; i < n;i++)
{
for(int j = i+1;j < n;j++)
{
Point tmp = (p[i] + p[j])/2;
ll tp = tmp.x*100000 + tmp.y;
mp[tp]++;
}
}
for(it = mp.begin(); it != mp.end();it++)
{
ll tp = it->first;
int cur = n - 2*it->second;
if(vis[tp]) cur--;
ans = min(ans, cur);
}
for(int i = 0;i < n;i++)
{
for(int j = i+1;j < n;j++)
{
Line L = Vertline(p[i], p[j]);
tp[L] += 2;
L = Connect(p[i], p[j]);
tp[L] += 0;
}
}
for(itt = tp.begin(); itt != tp.end();itt++)
{
int tmp = n - itt->second;
Line now = itt->first;
for(int i = 0;i < n;i++)
if(now.online(p[i])) tmp--;
ans = min(ans, tmp);
}
printf("%d\n", ans);
return 0;
}
I - Tourists
在树上求k到k的倍数的路径长度之和。
直接暴力枚举所有的边用LCA计算即可。时间复杂度。
#include <cstdio>
#include <cstring>
#include <cmath>
#include <vector>
#include <iostream>
#include <algorithm>
typedef long long ll;
using namespace std;
const int maxn = 200050;
const ll INF = (1LL << 62) - 1;
int n, a, b, no;
int pre[maxn][35], dep[maxn];
int head[maxn];
bool vis[maxn];
struct node
{
int to;
int nxt;
}e[maxn << 1];
void add(int a, int b)
{
e[no].to = b, e[no].nxt = head[a];
head[a] = no++;
e[no].to = a, e[no].nxt = head[b];
head[b] = no++;
}
void dfs(int u, int fa, int num)
{
pre[u][0] = fa;
dep[u] = num;
vis[u] = 1;
for(int i = head[u];i != -1;i = e[i].nxt)
{
int v = e[i].to;
if(vis[v]) continue;
dfs(v, u, num + 1);
}
return;
}
void init_lca()
{
for(int j = 1;(1<<j) <= n;j++)
{
for(int i = 1;i <= n;i++)
pre[i][j] = -1;
}
for(int j = 1;(1<<j) <= n;j++)
{
for(int i = 1;i <= n;i++)
{
if(pre[i][j - 1] != -1)
pre[i][j] = pre[pre[i][j - 1]][j - 1];
}
}
}
int lca(int x, int y)
{
if(dep[x] < dep[y]) swap(x, y);
int mlg = 0;
while((1<<mlg) <= dep[x]) mlg++;
mlg--;
for(int i = mlg;i >= 0;i--)
{
if(dep[x] - (1<<i) >= dep[y])
x = pre[x][i];
}
if(x == y) return x;
for(int i = mlg;i >= 0;i--)
{
if(pre[x][i] != -1 && pre[x][i] != pre[y][i])
x = pre[x][i], y = pre[y][i];
}
return pre[x][0];
}
int main()
{
scanf("%d", &n);
no = 0;
memset(head, -1, sizeof(head));
for(int i = 1;i < n;i++)
{
scanf("%d%d", &a, &b);
add(a, b);
}
dfs(1, 0, 1);
ll ans = 0;
init_lca();
for(int i = 1;i <= n;i++)
{
for(int j = i*2;j <= n;j += i)
{
int tp = lca(i, j);
ans += 1LL*(dep[i] - dep[tp] + dep[j] - dep[tp] + 1);
}
}
printf("%I64d\n", ans);
return 0;
}