编程之美复赛做出三个题目,比赛结束后,和集训队的队友讨论了一番,最终得出解题的结论

现附上第一题,第三题,第四题的解题报告。

2014微软编程之美 复赛 第一题:组队

时间限制:2000ms

单点时限:1000ms

内存限制:256MB

描述

有N支队伍,每个队伍有Ai个选手。

现在,这N支队伍想要进行交流:队伍之间交换选手,使得交换完后,每个队伍有且仅有1人和之前不同。

所以我们想知道,总共存在多少种可行的交换方案。两种交换方案不同,当且仅当,交换后的至少有一支队伍人员的集合不同。

 

输入

第一行一个整数T (1 ≤ T ≤ 10),表示数据组数。

接下来是T组输入数据,测试数据之间没有空行。

每组数据格式如下:

第一行一个整数N 。第二行N个整数,Ai (1 ≤ Ai≤ 100)

 

输出

对每组数据,先输出“Case x: ”,x表示是第几组数据。然后输出一个整数,表示有多少种可行的交换方案。由于答案可能很大,只需要输出除以109+7之后的余数即可。

 

数据范围

小数据:1 ≤ N ≤ 10

大数据:1 ≤  N ≤ 100

 

样例解释

3 * 3种交换方法都行。

 

样例输入

1
2
3 3

样例输出

Case 1: 9


这是一个这场比赛最水的题目,开始竟然wa了三次,因为输出格式,ACM题目做习惯了Case 后面喜欢加#号,太不值了,就是一道简单的错排题,可以自行谷歌错排公式。

AC代码(提供的AC代码是大数据小数据都可以通过的):

//by wangruixing
#include <iostream>
#include <cstdio>
#include <algorithm>
#define P 1000000007
#define LL long long
#define N 160
using namespace std;
LL f[N];
int main()
{
    int cnt,n,x;
    scanf("%d",&cnt);
    LL ans;
    f[1]=0;f[2]=1;
    for (int i=3;i<N;i++)
        f[i]=(f[i-1]+f[i-2])*(i-1)%P;
    for (int run=1;run<=cnt;run++)
    {
        scanf("%d",&n);
        ans=1;
        for (int i=1;i<=n;i++)
        {
            scanf("%d",&x);
            ans=ans*x%P;
        }
        printf("Case %d: ",run);
        cout <<ans*f[n]%P<<endl;
    }
    return 0;
}

第二题是一个投影题目,没有AC,但是思路是有的,就是一个投影,投影后的样子是一个矩形加一个半圆,注意是半圆,不是椭圆,一开始,想错了,没敢下手做,考虑几种情况,然后考虑两圆相交时的情况,扫一遍得到两个点,算出角度,然后长度就好求了,这个几何比较好解,但是当时没有做。

第三题:最大值

时间限制:4000ms

单点时限:2000ms

内存限制:256MB

描述

给定一棵点和边都带有正权的树,用distance(i, j)表示点i到点j的树上距离(即两点之间的最短路长度),用weight(i)表示点i的点权。

请求解在所有的i, j中,min{weight(i), weight(j)} * distance(i, j)的最大值。

 

输入

第一行一个整数T (1 ≤ T ≤ 10),表示数据组数。

接下来是T组输入数据,测试数据之间没有空行。

每组数据格式如下:

第一行一个整数N ,表示有N个节点。

第二行N个整数,分别表示weight(i) (1 ≤ weight(i) ≤ 10^9)。

接下来N - 1行,每行3个整数a, b, c (1 ≤ a, b ≤ N, 1 ≤ c ≤ 104),表示节点a和b之间有一条权值为c的边。

 

输出

对每组数据,先输出“Case x:

 

数据范围

小数据:1 ≤ N ≤ 1000

大数据:1 ≤ N ≤ 105

 

样例解释

只有i = 1, j = 2的可选方案,distance(1, 2) = 1, min{weight(i), weight(j)} = 1,所以最大值为1。

 

样例输入

1
2
1 2
1 2 1

样例输出

Case 1: 1


这个题目很厉害,我学会了一种方法,以前不知道的,就是转移树的重心。

AC代码:

#include <map>
#include <set>
#include <queue>
#include <ctime>
#include <cmath>
#include <string>
#include <cstdio>
#include <vector>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;#define all(a) a.begin(),a.end()
#define clr(a) memset(a,0,sizeof(a))
#define fill(a,b) memset(a,b,sizeof(a))
#define pb push_back
#define mp make_pairtypedef long long LL;
typedef vector<int> VI;
typedef pair<int,int> PII;
typedef vector<pair<int,int> > VII;const double eps = 1e-8;
const double pi = acos(-1.0);
const int Mod = 1e9+7;
const int N = 100000 + 10;
typedef vector<pair<int,int> >::iterator IT;struct St{
    int l, r, mm;
} st[N<<2];vector<pair<int,int> > vec[N];
int M[N];
int w[N], dis[N], IS[N], nid[N];
bool del[N];
int size[N], opt[N];
VI all, nn;
long long ans = 0;
void calc(int u, int fa){
    int i, v;
    size[u] = 1;
    opt[u] = 0;
    all.pb(u);
    for(int i=0;i<vec[u].size();i++){
        if((v = vec[u][i].first)==fa || del[v]) continue;
        calc(v, u);
        size[u] += size[v];
        opt[u] = max(opt[u], size[v]);
    }
}int GetHeart(int u){
    int i, minOpt = 1e9, heart, v;
    all.clear();
    calc(u, -1);
    for(i=0;i<all.size();i++){
        v = all[i];
        opt[v] = max(opt[v],size[u]-size[v]);
        if(opt[v] < minOpt){
            minOpt = opt[v];
            heart = v;
        }
    }
    return heart;
}bool cmp(int a, int b){
    return w[a]>w[b];
}int mark;
void go(int u, int par){
    nid[u] = mark;
    for(IT e = vec[u].begin(); e != vec[u].end(); ++e){
        if(!del[e->first] && e->first != par){
            dis[e->first]=dis[u]+e->second, go(e->first, u);
        }
    }
}void build(int l, int r, int i){
    st[i].l = l;
    st[i].r = r;
    st[i].mm = -1;
    if(l==r) return ;
    int mid = (l+r)>>1;
    build(l, mid, i<<1);
    build(mid+1, r, i<<1|1);
}int ask(int l, int r, int i){
    if(l == st[i].l && r == st[i].r){
        return st[i].mm;
    }
    int mid = (st[i].l+st[i].r)>>1;
    if(r<=mid) return ask(l, r, i<<1);
    else if(l>mid) return ask(l, r,i<<1|1);
    else return max(ask(l,mid,i<<1),ask(mid+1,r,i<<1|1));
}void make(int x, int val, int i){
    if(st[i].l==st[i].r){
        st[i].mm = max(st[i].mm, val);
        return ;
    }
    int mid = (st[i].l+st[i].r)>>1;
    if(x<=mid) make(x, val, i<<1);
    else make(x, val, i<<1|1);
    st[i].mm = max(st[i<<1].mm, st[i<<1|1].mm);
}void dfs(int u, int par){
    u = GetHeart(u);
    dis[u] = 0;
    int SZ = all.size(), idx = 0, i;
    sort(all.begin(), all.end(), cmp);
    for(IT e = vec[u].begin(); e != vec[u].end(); ++e){
        mark = ++idx;
        if(!del[e->first] && par != e->first){
            dis[e->first] = dis[u]+e->second, go(e->first, u);
        }
    }
    nid[u] = 0;
    build(0, idx, 1);
    for(i=0;i<all.size();++i){
        make(nid[all[i]], dis[all[i]], 1);
        int res;
        if(nid[all[i]] != 0) res = ask(0, nid[all[i]]-1, 1); else res = -1;
        if(nid[all[i]] != idx) res = max(res, ask(nid[all[i]]+1, idx, 1));
        if(res != -1) ans = max(ans, (long long)w[all[i]]*(res+dis[all[i]]));
    }    del[u] = 1;
    for(IT e = vec[u].begin(); e != vec[u].end(); ++e){
        if(!del[e->first] && e->first != par)
        dfs(e->first , u);
    }
}int main(){
    int tt, n, i, a, b, c, cal=0;
    scanf("%d",&tt);
    while(tt--){
        scanf("%d",&n);
        for(i=1;i<=n;++i){
            scanf("%d",&w[i]);
            vec[i].clear();
        }
        for(i=1;i<n;++i){
            scanf("%d%d%d",&a,&b,&c);
            vec[a].pb(mp(b,c));
            vec[b].pb(mp(a,c));
        }
        ans = 0;
        memset(del,0,sizeof(del));
        dfs(1, -1);
        printf("Case %d: ",++cal);
        cout << ans << endl;
    }
    return 0;
}

 

第四题:广场建设

时间限制:2000ms

单点时限:1000ms

内存限制:256MB

描述

在某个外星城市中,有n个居民点,他们分布在三维空间里,坐标分别为(xi, yi, zi) (i = 1, 2, … ,n)。现在我们要建一个广场,广场是通过原点的一个平面Ax + By + Cz = 0(A, B, C不全为0)。请你帮忙确定这个广场的倾斜方向,使得n个居民点到广场的距离的平方和最小。

 

输入

输入包含多组测试数据,第一行是一个整数T (1 ≤ T ≤ 10),表示测试数据组数。

对于每组测试数据:

第一行是一个整数n,表示点的个数。(1 ≤ n ≤ 100)

接下来的n行,每行三个整数x, y, z,之间用一个空格分隔。

 

输出

对每组测试数据输出一行,格式为“Case x: A B C”。x表示测试数据组数,A, B, C的意义见题目描述,输出任意一组解即可。如果根据答案所计算的距离平方和与最优解的绝对误差或相对误差在10-6以内,则认为答案正确。

 

数据范围

小数据:1 ≤ n ≤ 2

大数据:1 ≤ n ≤ 100

 

样例输入

2
2
0 0 1
0 1 0
2
0 0 0
0 0 0

样例输出

Case 1: 1.0 0.0 0.0

Case 2: 1.0 0.0 0.0


这个题目真是让我长见识了,用到了随机算法,一般很少用,先随机算法得到一些点,通过二分来缩小范围,当缩小到一个精度范围内,就得出所要的点,这个方法比较巧妙,在计算几何里面经常会用到。这个题当时只A掉了小数据,没有想到这种方法,现在分享给大家。

AC代码:

#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <vector>
#include <cmath>
#include <ctime>
#include <cstdlib>using namespace std;
const double eps = 1e-10 ;
int sgn(double x) { return (x < -eps) ? -1 : (x > eps); }struct PT {
	double x, y, z;
	PT () {}
	PT (double x, double y, double z) : x(x), y(y), z(z) {}
}	;PT pt[105];
double X2, Y2, Z2, XY, XZ, YZ;double work(PT pp)
{
	double a = pp.x, a2 = a * a;
	double b = pp.y, b2 = b * b;
	double c = pp.z, c2 = c * c;
	double now = a2 * X2 + b2 * Y2 + c2 * Z2 + a * b * XY * 2 + a * c * XZ * 2 + b * c * YZ * 2;
	return now / (a2 + b2 + c2);
}int main()
{
	freopen("1.txt", "r", stdin);
	int cas, T;
	srand(330598937);

	for (cas=scanf("%d", &T); cas<=T; cas++)
	{
		int n;
        scanf ("%d", &n);

		X2 = Y2 = Z2 = XY = XZ = YZ = 0 ;
		for (int i=0; i<n; i++) 
		{
			double x, y, z;
			scanf("%lf %lf %lf", &x, &y, &z);
			X2 += x * x ;
			Y2 += y * y ;
			Z2 += z * z ;
			XY += x * y ;
			XZ += x * z ;
			YZ += y * z ;
		}
		PT bt(1, 1, 1);

		double ans = work(bt);
		for (double step = 1e6; step > eps; step /= 2)
		{
			for (int i=1; i<=500; i++)
			{
				double th1 = rand();
				double th2 = rand();
				PT p(bt.x + step * sin(th1), bt.y + step * cos(th1), bt.z + step * cos(th2));
				double tmp = work(p);
				if (tmp < ans) ans = tmp, bt = p;
			}
		}

		printf("Case %d: ", cas);
		printf("%.8f %.8f %.8f\n", bt.x, bt.y, bt.z);
	}
    return 0;
}