1. 程式人生 > >國慶七天樂Day1 - 2015ICPC長春站 【8/13】

國慶七天樂Day1 - 2015ICPC長春站 【8/13】

題目連結

A - Too Rich

有面值為1.5.10.20.50.100.200.500.1000.2000的鈔票若干張,要求用盡可能多數目的鈔票湊出p元。

考慮先求出給出的所有金額之和sum,那麼問題就等價於用盡可能少的鈔票湊出sum-p元。如果每一種面值都能整除較小的那個面值,那麼只要直接貪心即可。但此題由於50和500的存在不能直接貪心,例如:p = 600,手中有3張200和1張500。

此時如果貪心的話是湊不出600的,但實際上可以。考慮到這種情況的存在,在從後往前遍歷每種面值的鈔票的時候,都分為全部使用與留一張這兩種情況,可以用dfs實現。

#include <cstdio>
#include <cstring>
#include <cmath>
#include <vector>
#include <iostream>
#include <algorithm>
typedef long long ll;
using namespace std;

const int maxn = 2505;
const ll INF = (1LL << 62) - 1;

int t;
ll a[15], p, ans;
ll w[15] = {1, 5, 10, 20, 50, 100, 200, 500, 1000, 2000};

void dfs(int u, ll p, ll res)
{
    if(u < 0)
    {
        if(p == 0) ans = min(ans, res);
        return;
    }
    ll tmp = p/w[u];
    tmp = min(tmp, a[u]);
    dfs(u-1, p - tmp*w[u], res + tmp);
    if(tmp > 0)
        dfs(u-1, p - (tmp-1)*w[u], res + tmp - 1);
}

int main()
{
    scanf("%d", &t);
    while(t--)
    {
        scanf("%lld", &p);
        ll sum = 0,cnt = 0;
        for(int i = 0;i < 10;i++)
        {
            scanf("%lld", &a[i]);
            sum += a[i]*w[i];
            cnt += a[i];
        }
        if(sum < p) {puts("-1"); continue;}
        sum -= p;
        ans = INF;
        dfs(9, sum, 0);
        if(ans == INF) puts("-1");
        else printf("%lld\n", cnt - ans);
    }
    return 0;
}

B - Count a*b

定義:f(n)=\sum _{i=0}^{n-1}\sum _{j=0}^{n-1}[(i*j) modn != 0],g(n)=\sum _{m|n}^{ }f(m),求g(n)。

設:h(n)=\sum _{i=0}^{n-1}\sum _{j=0}^{n-1}[(i*j) modn == 0],那麼f(n)=n^{2}-h(n)。考慮化簡h(n):

h(n)=\sum _{i=1}^{n}\sum _{j=1}^{n}[gcd(i*j, n)==n]=\sum_{i=1}^{n}\frac{n}{gcd(i,n)}=\sum_{i=1}^{n}gcd(i,n)

h(n)=\sum_{i=1}^{n}\sum_{d|gcd(i,n)}\phi (d)=\sum_{d|n}d\sum_{i=1}^{n/d}[gcd(i, n/d)==1]=\sum_{d|n}d\phi(d)

又有:g(n) = \sum_{m|n}m^{2}-\sum_{m|n}h(m),且h(n)與\sum_{m|n}h(m)都是積性函式。

對於n=p^{\alpha },推導可知\sum_{m|n}h(m)=(\alpha+1)p^{\alpha}那麼推廣就可有

\sum_{m|n}h(m)=n\prod_{i=1}^{k} (\alpha_{i}+1)

最終g(n) = \sum_{m|n}m^{2}-n\prod_{i=1}^{k} (\alpha_{i}+1)

#include <cstdio>
#include <cstring>
#include <cmath>
#include <queue>
#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;
typedef unsigned long long ll;

const int maxn = 32005;

int t, pri[maxn], num;
ll n, cur, ans, res;
int p[30], len, cnt[30];
bool flag[maxn];

void init()
{
    memset(flag, 0, sizeof(flag));
    num = 0;
    for(int i = 2;i < maxn;i++)
    {
        if(!flag[i])
        {
            pri[++num] = i;
            for(int j = 2*i;j < maxn;j += i)
                flag[j] = true;
        }
    }
}

int main()
{
    init();
    scanf("%d", &t);
    while(t--)
    {
        scanf("%llu", &n);
        ans = 1, res = n;
        len = 0;
        memset(cnt, 0, sizeof(cnt));
        for(int i = 1;i <= num;i++)
        {
            if(pri[i]*pri[i] > n) break;
            if(n % pri[i] == 0) p[len++] = pri[i];
            while(n % pri[i] == 0)
            {
                cnt[len-1]++;
                n /= pri[i];
            }
        }
        if(n > 1) {p[len] = n; cnt[len++] = 1;}
        for(int i = 0;i < len;i++)
        {
            res *= (cnt[i]+1);
            cur = 1;
            for(int j = 1;j <= cnt[i];j++)
                cur = cur*p[i]*p[i] + 1;
            ans *= cur;
        }
        ans -= res;
        printf("%llu\n", ans);
    }
    return 0;
}

E - Rebuild

給出一個多邊形,要求以每個頂點為圓心作圓,使得每條邊的兩個頂點的圓的半徑之和為這條邊的長度。問是否存在合法方案,若存在則求出圓的總面積的最小值。

可以發現,如果n是奇數,那麼如果有解則必定只存在一組解。而n是偶數時,如果有解則可行解是一個區間。

先判斷是否有解, 對於n條邊可以得到n個不等式,解這個不等式組,如果能得到合法的解區間則有解。在有解的情況下,如果n為奇數則只需要求出唯一解,如果n是偶數則需要在解區間上三分求出總面積最小的點。

#include <cstdio>
#include <cstring>
#include <cmath>
#include <queue>
#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;

const int maxn = 10050;
const double eps = 1e-8;
const double pi = acos(-1.0);

int t, n;
double res[maxn];
struct node
{
    double x, y;
    double len;
}e[maxn];
int sgn(double x) {return fabs(x) < eps ? 0 : (x < 0 ? -1 : 1);}
double dis(node a, node b)
{
    return sqrt((a.x - b.x)*(a.x - b.x) + (a.y - b.y)*(a.y - b.y));
}
bool Equal(double a, double b) {return sgn(a - b) == 0;}

double calArea(double x)
{
    double res = x*x, cur = x;
    for(int i = 0;i < n-1;i++)
    {
        cur = e[i].len - cur;
        res += cur*cur;
    }
    return res*pi;
}

int main()
{
    scanf("%d", &t);
    while(t--)
    {
        scanf("%d", &n);
        for(int i = 0;i < n;i++)
            scanf("%lf%lf", &e[i].x, &e[i].y);
        for(int i = 0;i < n;i++)
            e[i].len = dis(e[i], e[(i+1)%n]);
        res[0] = 0;
        for(int i = 0;i < n;i++)
        {
            if(i & 1) res[0] -= e[i].len;
            else res[0] += e[i].len;
        }
        if(n & 1)
        {
            res[0] /= 2;
            for(int i = 1;i < n;i++)
                res[i] = e[i-1].len - res[i-1];
            if(!Equal(e[n-1].len - res[n-1], res[0])) puts("IMPOSSIBLE");
            else
            {
                bool vis = 0;
                for(int i = 0;i < n;i++)
                    if(sgn(res[i]) < 0) {vis = 1; break;}
                if(vis) {puts("IMPOSSIBLE"); continue;}
                double ans = 0;
                for(int i = 0;i < n;i++) ans += (res[i]*res[i]);
                printf("%.2lf\n", ans*pi);
                for(int i = 0;i < n;i++) printf("%.2lf\n", res[i]);
            }
        }
        else
        {
            if(sgn(res[0]) != 0) {puts("IMPOSSIBLE"); continue;}
            double L = 0, R = e[0].len, cur = e[0].len, mid, mmid;
            for(int i = 1;i < n;i++)
            {
                if(i & 1) {cur -= e[i].len; L = max(L, cur);}
                else {cur += e[i].len; R = min(R, cur);}
            }
            if(L > R) {puts("IMPOSSIBLE"); continue;}
            for(int i = 0;i < 120;i++)
            {
                mid = (L + R)/2;
                mmid = (mid + R)/2;
                double sa = calArea(mid), sb = calArea(mmid);
                if(sa - sb > eps) L = mid;
                else R = mmid;
            }
            res[0] = L;
            bool flag = 0;
            for(int i = 0;i < n - 1;i++)
            {
                res[i+1] = e[i].len - res[i];
                if(sgn(res[i+1]) < 0) flag = 1;
            }
            if(flag) puts("IMPOSSIBLE");
            else
            {
                printf("%.2lf\n", calArea(L));
                for(int i = 0;i < n;i++)
                    printf("%.2lf\n", res[i]);
            }
        }
    }
    return 0;
}

F - Almost Sorted Array

給出一個數列,最多去掉一個數字,問能否將其變成單調不上升或單調不下降。

簽到題,只要最長不上升/不下降子序列的長度不小於n-1即可。

 

G - Dancing Stars on Me

給出n個整數點的座標,問這些點是否為正n邊形的n個頂點。

考慮到給出的座標都為整數,那麼當且僅當n=4時有解。

判斷正方形只需要先判四條邊是否相等,再判對角線是否相等。遍歷四個點的4!種排列。

#include <cstdio>
#include <cstring>
#include <cmath>
#include <vector>
#include <iostream>
#include <algorithm>
typedef long long ll;
using namespace std;

const int maxn = 55;
const ll mod = 1e9 + 7;
const double eps = 1e-8;

int t, n;
struct node
{
    ll x, y;
}e[105];

ll dis(node a, node b)
{
    return (a.x - b.x)*(a.x - b.x) + (a.y - b.y)*(a.y - b.y);
}

bool judge()
{
    int a[] = {0, 1, 2, 3};
    do
    {
        bool flag = 0;
        ll tmp = dis(e[a[0]], e[a[3]]);
        for(int i = 0;i < 3;i++)
        {
            if(tmp != dis(e[a[i]], e[a[i+1]]))
            {
                flag = 1;
                break;
            }
        }
        if(flag) continue;
        if(dis(e[a[0]], e[a[2]]) == dis(e[a[1]], e[a[3]])) return 1;
    }while(next_permutation(a, a + 4));
    return 0;
}

int main()
{
    scanf("%d", &t);
    while(t--)
    {
        scanf("%d", &n);
        for(int i = 0;i < n;i++)
            scanf("%lld%lld", &e[i].x, &e[i].y);
        if(n != 4) {puts("NO"); continue;}
        if(judge()) puts("YES");
        else puts("NO");
    }
    return 0;
}

H - Partial Tree

定義了樹的權值為所有點的權值之和, 每個點的權值為f(d),其中d為這個點的度。求樹的最大可能權值。

一個有n個點的樹,所有點的權值之和為2n-2。顯然每個點的度最少要為1,剩下的n-2個度可以自由分配到各個點上,因為只要總的度數為2n-2,一定存在一種結構使得這種分配方案是合法的。

用dp[i]表示分配了i個度數時最大總權值,那麼dp[ i+j ] = max{dp[ i ] + f(j+1) - f(1)}。其中初始化dp[0] = n*f(1),答案即為dp[n-2]。

#include <cstdio>
#include <cstring>
#include <cmath>
#include <vector>
#include <iostream>
#include <algorithm>
typedef long long ll;
using namespace std;

const int maxn = 2505;
const ll mod = 1e9 + 7;
const ll INF = (1LL << 62) - 1;
const double eps = 1e-8;

int t, n;
ll f[maxn], dp[maxn];

int main()
{
    scanf("%d", &t);
    while(t--)
    {
        scanf("%d", &n);
        for(int i = 1;i < n;i++)
            scanf("%lld", &f[i]);
        for(int i = 0;i <= n;i++)dp[i] = -INF;
        dp[0] = n*f[1];
        for(int i = 0;i <= n-2;i++)
        {
            for(int j = 1;i + j <= n-2;j++)
                dp[i+j] = max(dp[i+j], dp[i] + f[j+1] - f[1]);
        }
        printf("%lld\n", dp[n-2]);
    }
    return 0;
}

J - Chip Factory

給出一個數列a,要求找出其中的三項ai,aj,ak,使得(a_{i}+a_{j})\bigoplus a_{k}最大,求這個最大值。

考慮在O(n^3)的暴力的基礎上進行優化,我們可以O(n^2)地求出兩項之和,然後在剩餘的部分裡找出一個異或值最大的。考慮到使異或最大是從高位到低位依次計算,這個過程就可以用字典樹來實現。每次將需要相加的兩個數從字典樹刪去,計算完成後再將它們插入進去。總的時間複雜度為O(n^{2}logn)

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 5e4+10;
const ll INF = (1LL << 62) - 1;
const int len = 32;
const double eps = 1e-8;

ll a[maxn];
struct Trie{
	int root, tot, nex[maxn][2], num[maxn];
	ll End[maxn];
	inline int newnode(){
		memset(nex[tot], -1, sizeof(nex[tot]));
		End[tot] = 0;
		num[tot] = 0;
		return tot++;
	}
	inline void init(){
		tot = 0;
		root = newnode();
	}
	inline void Insert(ll x){
		int p = root;
		for(int i = 32; i >= 0; i--){
			int idx = ((1LL<<i)&x)? 1: 0;
			if(nex[p][idx] == -1)nex[p][idx] = newnode();
			p = nex[p][idx];
			num[p]++;
		}
		End[p] = x;
	}
	inline update(ll x, int d){
		int cur = root;
		for(int i = 32; i >= 0; i--){
			int index = (x&(1LL<<i))? 1: 0;
			cur = nex[cur][index];
			num[cur] += d;
		}
	}
	inline ll Search(ll x){
		int p = root;
		for(int i = 32; i >= 0; i--){
			int idx = ((1LL<<i)&x)? 1: 0;
			if(nex[p][idx^1] && num[nex[p][idx^1]])p = nex[p][idx^1];
			else p = nex[p][idx];
		}
		return x^End[p];
	}
}tr;
int main(){
    int T;
    scanf("%d", &T);
    while(T--){
		int n;
		scanf("%d", &n);
		tr.init();
		for(int i = 0; i < n; i++){
			scanf("%lld", &a[i]);
			tr.Insert(a[i]);
		}
		ll ans = 0;
		for(int i = 0; i < n; i++){
			for(int j = i+1; j < n; j++){
				tr.update(a[i], -1);
				tr.update(a[j], -1);
				ans = max(ans, tr.Search(a[i]+a[j]));
				tr.update(a[i], 1);
				tr.update(a[j], 1);
			}
		}
		printf("%lld\n", ans);
    }
    return 0;
}

L - House Building

若干個1*1木塊放在n*m格子中,給出擺放的情況,求除底面以外的表面積。

簽到題,每一行和每一列掃一遍,將相鄰兩個位置高度差求和,再加上俯視面積即可。

#include <cstdio>
#include <cstring>
#include <cmath>
#include <vector>
#include <iostream>
#include <algorithm>
typedef long long ll;
using namespace std;

const int maxn = 55;
const ll mod = 1e9 + 7;

int t, n, m;
ll a[maxn][maxn];

ll Abs(ll a) {return a < 0 ? -a : a;}

int main()
{
    scanf("%d", &t);
    while(t--)
    {
        scanf("%d%d", &n, &m);
        for(int i = 1;i <= n;i++)
        {
            for(int j = 1;j <= m;j++)
                scanf("%lld", &a[i][j]);
        }
        ll ans = 0;
        for(int i = 1;i <= n;i++)
        {
            ll now = 0;
            for(int j = 1;j <= m;j++)
            {
                ans += Abs(now - a[i][j]);
                now = a[i][j];
            }
            ans += now;
        }
       for(int j = 1;j <= m;j++)
        {
            ll now = 0;
            for(int i = 1;i <= n;i++)
            {
                ans += Abs(now - a[i][j]);
                now = a[i][j];
            }
            ans += now;
        }
        for(int i = 1;i <= n;i++)
        {
            for(int j = 1;j <= m;j++)
            {
                if(a[i][j] != 0)
                    ans++;
            }
        }
        printf("%lld\n" ,ans);
    }
    return 0;
}