国庆七天乐Day2 - NAIPC 2016 【7/11】

题目链接

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,一开始想搜索,然后O(15!)就TLE了……

正解是状压dp,dp[i][S]表示用i种矩形覆盖S这个集合浪费的最小面积。

for(int k = p;k;k = (k-1)&p) 这个操作可以枚举二进制p的所有子集,枚举所有方案的所有子集的时间复杂度是O(3^{n})。学到了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]}转移,求出所有的方案数,最后减去没有山峰的\left \lfloor \frac{n}{w} \right \rfloor种情况即可。

#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里。对于一条直线,需要通过斜率和截距来保存。斜率用\Delta y\Delta x来表示,而截距d=y_{0}-\frac{\Delta y}{\Delta x}x_{0},可以储存\Delta xd=\Delta xy_{0}-\Delta yx_{0},它一定是一个整数。对于这三个整数,为了去重,我们将其除以三者的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计算即可。时间复杂度O(nlog^{2}n)

#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;
}

猜你喜欢

转载自blog.csdn.net/NPU_SXY/article/details/82930106
今日推荐