2013ACM多校联合(2)_HUT 解题报告
感谢其它高校的负责人,严格的审题等工作~~~。
预祝大家本场训练赛玩的高兴。。。
训练赛 RankList
解题报告转摘自 Lyush
A.简单的想法题。使用hash表或者是map存储所有数,然后从最小的数开始找从这个数开始的连续P倍数的个数X,那么需要删除的数的个数为X/2。
#include <iostream> #include <cstring> #include <cstdio> #include <cstdlib> #include <map> #include <cassert> #include <ctime> using namespace std; int N, K; map<long long,bool>mp; int main() { map<long long,bool>::iterator it; int sum; while (scanf("%d %d", &N, &K) == 2) { sum = 0; mp.clear(); int c; for (int i = 0; i < N; ++i) { scanf("%d", &c); mp[c] = true; } int cnt; long long t; for (it = mp.begin(); it != mp.end(); ++it) { cnt = 0; long long t = it->first; while (mp[t]) { mp[t] = false; t = K * t; ++cnt; } sum += cnt / 2; } printf("%d\n", N - sum); } return 0; }
B.组合数学题,通过经典的一一对应原则推导出答案为N^(N-2)。http://wenku.baidu.com/view/2e1ab2757fd5360cba1adbba.html
Cayley定理在组合数学中的应用。
#include <iostream> #include <cmath> #include <cstring> #include <cstdio> #include <string> #include <stdlib.h> using namespace std; typedef long long LL; const LL M=1e9+7; LL Pow( LL a, LL b) { LL ans=1; while( b ){ if( b&1 ){ ans*=a; ans%=M; } a*=a; a%=M; b>>=1; } return ans; } int main( ) { LL N; while( scanf( "%lld", &N )!=EOF ){ printf( "%lld\n", Pow( N, N-2 ) ); } return 0; }
C.计算几何。该题一个简单的做法是先找出三个点的外心,然后用极坐标每次旋转72°生成剩下的点。
#include <cstring> #include <cstdio> #include <cstdlib> #include <algorithm> #include <iostream> #include <cmath> #include <vector> using namespace std; const double PI = acos(-1); struct point{double x,y;}; struct line{point a,b;}; vector<point>v; point p[3], cc; point intersection(line u,line v){ point ret=u.a; double t=((u.a.x-v.a.x)*(v.a.y-v.b.y)-(u.a.y-v.a.y)*(v.a.x-v.b.x)) /((u.a.x-u.b.x)*(v.a.y-v.b.y)-(u.a.y-u.b.y)*(v.a.x-v.b.x)); ret.x+=(u.b.x-u.a.x)*t; ret.y+=(u.b.y-u.a.y)*t; return ret; } point circumcenter(point a,point b,point c){ line u,v; u.a.x=(a.x+b.x)/2; u.a.y=(a.y+b.y)/2; u.b.x=u.a.x-a.y+b.y; u.b.y=u.a.y+a.x-b.x; v.a.x=(a.x+c.x)/2; v.a.y=(a.y+c.y)/2; v.b.x=v.a.x-a.y+c.y; v.b.y=v.a.y+a.x-c.x; return intersection(u,v); } double dist(point a, point b) { return sqrt(double( (a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y) )); } bool cmp(point a, point b) { if (fabs(a.x - b.x) > 1e-6) { return a.x < b.x; } else { return a.y < b.y; } } int main() { double R; point info; while (scanf("%lf %lf", &p[0].x, &p[0].y) != EOF) { v.clear(); for (int i = 1; i < 3; ++i) { scanf("%lf %lf", &p[i].x, &p[i].y); } cc = circumcenter(p[0], p[1], p[2]); R = dist(p[0], cc); double phi = atan2(p[0].y-cc.y, p[0].x-cc.x); // 求出第一个点的角度 for (int i = 0; i < 5; ++i) { info.x = R * cos(phi) + cc.x, info.y = R * sin(phi) + cc.y; v.push_back(info); phi += 72.0*PI/180.0; } sort(v.begin(), v.end(), cmp); for (int i = 0; i < 5; ++i) { int flag = 0; for (int j = 0; j < 3; ++j) { if (fabs(v[i].x - p[j].x) < 1e-6 && fabs(v[i].y - p[j].y) < 1e-6) { flag = 1; break; } } if (!flag) { printf("%.2f %.2f\n", v[i].x, v[i].y); } } } return 0; }
也可以通过圆求对称点来解
#include<stdio.h> #include<string.h> #include<stdlib.h> #include<math.h> #include<algorithm> using namespace std; const double esp = 1e-8; int sign( double x ) { return x<-esp ? -1 : (x > esp); } struct Point { double x, y; void read(){scanf("%lf%lf",&x,&y);} bool operator < (Point tmp) const { if( sign(x-tmp.x) == 0 ) return y < tmp.y; else return x < tmp.x; } }p[5], mid, ans[2]; double L; void get_middle_point(double &x,double &y,double x1,double y1,double x2,double y2,double x3,double y3) { double tempx1,tempx2,tempy1,tempy2; double k1,k2; if(y1==y2) { x=(x1+x2)/2.0; tempx1=(x1+x3)/2.0; tempy1=(y1+y3)/2.0; k1=(x3-x1)/(y1-y3); y=k1*(x-tempx1)+tempy1; } else if(y1==y3) { x=(x1+x3)/2.0; tempx1=(x1+x2)/2.0; tempy1=(y1+y2)/2.0; k1=(x2-x1)/(y1-y2); y=k1*(x-tempx1)+tempy1; } else if(y2==y3) { x=(x2+x3)/2.0; tempx1=(x1+x3)/2.0; tempy1=(y1+y3)/2.0; k1=(x3-x1)/(y1-y3); y=k1*(x-tempx1)+tempy1; } else { //printf("i am here\n"); tempx1=(x1+x3)/2.0; tempy1=(y1+y3)/2.0; k1=(x3-x1)/(y1-y3); tempx2=(x1+x2)/2.0; tempy2=(y1+y2)/2.0; k2=(x2-x1)/(y1-y2); //printf("%lf %lf %lf %lf %lf %lf\n",tempx1,tempy1,k1,tempx2,tempy2,k2); x=((tempy2-tempy1)+k1*tempx1-k2*tempx2)/(k1-k2); y=k1*(x-tempx1)+tempy1; } } Point duicheng( Point P, Point a1, Point a2 ) { Point res; double x = P.x, y = P.y; double x1 = a1.x, y1 = a1.y; double x2 = a2.x, y2 = a2.y; if( y1 == y2 || x1 == x2 ) { if( x1 == x2){ res.x = 2*x1-x; res.y = y; } else{ res.x = x; res.y = 2*y1-y;} return res; } else{ double k = (y1-y2)/(x1-x2), b = y1 - x1*k; res.x = 2*(x+k*y-k*b)/(k*k+1)-x; res.y = 2*(k*x+k*k*y+b)/(k*k+1)-y; return res; } } double dist( Point a, Point b ) { double dis = (a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y); return sqrt(dis); } bool compare( Point a, Point b ) { for(int i = 0; i < 3; i++) { if( (sign( a.x-p[i].x ) == 0) && (sign( a.y-p[i].y)) == 0 ) return false; if( (sign( b.x-p[i].x ) == 0) && (sign( b.y-p[i].y)) == 0 ) return false; } return true; } int main() { int T; scanf("%d", &T); while( T-- ) { for(int i = 0; i < 3; i++) p[i].read(); get_middle_point( mid.x, mid.y, p[0].x,p[0].y,p[1].x,p[1].y,p[2].x,p[2].y); L = dist( mid, p[0] ); for(int i = 0; i < 3; i++) { ans[0] = duicheng( p[(i+1)%3], p[i],mid ); ans[1] = duicheng( p[(i+2)%3], p[i],mid ); if( compare(ans[0],ans[1]) ) break; } sort( ans, ans+2 ); printf("%.2f %.2f\n", ans[0].x, ans[0].y ); printf("%.2f %.2f\n", ans[1].x, ans[1].y ); } return 0; }
D.动态规划。题意是求将一个串改造成回文串的最少花费。给出所有组成串的字母添加和删除的花费。设dp[i][j]表示[i,j]子串被改造成回文串的最少开销是多少。那么有动态规划方程:
dp[i][j] = min(dp[i+1][j] + cost[i], dp[i][j-1] + cost[j]); 其中cost[i] = min(add[i], del[i])存储删除和添加节点的较小的花费。如果str[i] == str[j]的话就多出一种选择dp[i-1][j-1]。
#include <cstdlib> #include <cstdio> #include <cstring> #include <iostream> #include <algorithm> using namespace std; int N, M, dp[2005][2005]; int cost[30]; char str[2005]; int DP() { for (int i = 1; i < M; ++i) { for (int j = 1; j+i <= M; ++j) { int a = j, b = j + i, ll = str[a]-'a', rr = str[b]-'a'; dp[a][b] = min(dp[a+1][b] + cost[ll], dp[a][b-1] + cost[rr]); if (ll == rr) dp[a][b] = min(dp[a][b], dp[a+1][b-1]); } } return dp[1][M]; } int main() { char s[5]; int a, b; while (scanf("%d %d", &N, &M) == 2) { scanf("%s", str+1); for (int i = 1; i <= N; ++i) { scanf("%s %d %d", s, &a, &b); cost[s[0]-'a'] = min(a, b); } printf("%d\n", DP()); } return 0; }
E.
将 b1,b2,...,bn分解素因子,化成 形式.
总共有 K 种不同的 素因子
令, 表示 第i 种素因子的次数.
则, 我们可以先分开考虑第 i 种素数,其共有m个,将其放置在 n个盒子中,其中盒子不同,可以为空.
这里有一个 组合数学关于 球放置于盒子的 问题. 具体可参考 jian1573博客 (http://www.cnblogs.com/jian1573/archive/2011/09/18/2180433.html)
其方案数为
因为总共有 K 种 不同素数, 则总方案数为:
此时问题,还没有被完全解决, 因为, 题目要求 ai > 1, 意味着, 任意个盒子,不可同时都为空,
而我们上面的计算,是先假设其可以为空的情形.所以,我们需要减去所有为空的情况,剩下的才是我们的最终结果.
这里通过 假设,至少1个盒子为空, 至少2个盒子为空, ...,至少 n-1个盒子为空( 必定有1个盒子不为空,所以不能取到n )
使用容斥原理来计算. 关键点
一, 假设 1个盒子必定为空, 我们可以通过 假定盒子总数量为 n-1 个, 公式转换成 得出. 多个盒子为空同上.
二, 假设 1个盒子必定为空, 对于剩下的 n-1个盒子, 因为我们计算的是组合情形, 其中还是会出现其它盒子为空.意味着
有重复的情形. 所以这里需要用到容斥原理, 另外这里因为是 集合的并, 容斥的计算是 减奇加偶.
#include<cstdio> #include<cstring> #include<cstdlib> #include<assert.h> #include<algorithm> #include<map> #include<cmath> using namespace std; typedef long long LL; const int mod = 1e9+7; int b[21], a[1010], n, tot; LL C[1100][1100]; map<int,int>mp; void init(){ for(int i = 0; i <= 1000; i++) C[i][0] = 1, C[i][i] = 1; for(int i = 2; i <= 1000; i++) for(int j = 1; j < i; j++) C[i][j] = (C[i-1][j-1]+C[i-1][j])%mod; } void deal(){ mp.clear(); for(int i = 0; i < n; i++){ int tmp_max = (int)sqrt(1.*b[i]); int t = b[i]; for(int j = 2; j <= tmp_max; j++) { if( t%j == 0 ){ if( mp.count(j) == 0 ) mp[j] = 0; int cnt = 0; while( t%j == 0 ) (t/=j), cnt++; mp[j] += cnt; } } if( t > 1 ){ if( mp.count(t) == 0 ) mp[t] = 1; else mp[t]++; } } int idx = 0; memset( a, 0, sizeof(a)); for( map<int,int>::iterator it = mp.begin(); it != mp.end(); it++ ) a[idx++] = it->second; tot = mp.size(); } void solve(){ LL ans = 0; for(int i = 0; i < n; i++){ LL tmp = 1; for(int j = 0; j < tot; j++){ assert( (((n-1-i+a[j])>=0)&&(n-1-i+a[j])<=1000) ); tmp = tmp*C[ n-1-i+a[j] ][ n-1-i ]%mod; } tmp = tmp*C[n][i]%mod; if( i&1 ) tmp = -tmp; ans = (ans+tmp+mod)%mod; } printf("%lld\n", ans); } int main(){ init(); int T; scanf("%d", &T); while( T-- ){ scanf("%d", &n); for(int i = 0; i < n; i++) scanf("%d", &b[i] ); deal(); solve(); } return 0; }
F.并查集+最短路。通过并查集处理结盟的点,然后从集合中选择一个到目标点最短的距离输出。
解法一:
#include<iostream> #include<stdio.h> #include<string.h> using namespace std; const int inf = 0x7f7f7f7f; const int MAXV = 1002; int UFS[MAXV]; int Find(int x) { return UFS[x]=(UFS[x]!=x?Find(UFS[x]):x); } void Union(int x,int y) { UFS[Find(x)]=Find(y); } char o; int n,m,k,u,v,w,x,y,a[MAXV][MAXV]; int main() { while(scanf("%d %d %d",&n,&m,&k)!=EOF) { memset(a,inf,sizeof(a)); for(int i=0;n>i;i++) { UFS[i]=i; } for(int i=0;m>i;i++) { scanf("%d %d %d",&u,&v,&w); a[u][v]=min(a[u][v],w); } for(int z=0;n>z;z++) { for(int i=0;n>i;i++) { if(a[i][z]!=inf) { for(int j=0;n>j;j++) { if(a[z][j]!=inf) { a[i][j]=min(a[i][j],a[i][z]+a[z][j]); } } } } } for(int i=0;k>i;i++) { // getchar(); char ch[10]; scanf("%s %d %d",&ch,&x,&y); o = ch[0]; if(o=='I') Union(x,y); else { int ans=inf; for(int i=0;n>i;i++) { if(Find(x)==Find(i)) { ans=min(ans,a[i][y]); } } printf("%d\n",ans!=inf?ans:-1); } } } return 0; }
解法二:
#include<iostream> #include<string.h> #include<stdio.h> #include<queue> using std::queue; const int inf = 0x7f7f7f7f; const int MAXV = 1002; const int MAXE = 500002; struct node { int v,w; }G[MAXE]; int _index,pre[MAXV],next[MAXE]; void clear(void) { _index=0; memset(pre,-1,sizeof(pre)); } void add(int u,int v,int w) { G[_index].v=v; G[_index].w=w; next[_index]=pre[u]; pre[u]=_index++; } int UFS[MAXV]; int Find(int x) { return UFS[x]=(UFS[x]!=x?Find(UFS[x]):x); } void Union(int x,int y) { UFS[Find(x)]=Find(y); } int dis[MAXV]; bool inQ[MAXV]; int SPFA(int src,int des,int n) { queue<int> Q; memset(dis,inf,sizeof(dis)); memset(inQ,false,sizeof(inQ)); for(int i=0;n>i;i++) { if(Find(i)==Find(src)) { dis[i]=0; inQ[i]=true; Q.push(i); } } int u,v,w; while(!Q.empty()) { u=Q.front(); inQ[u]=false; Q.pop(); for(int i=pre[u];i!=-1;i=next[i]) { v=G[i].v; w=G[i].w; if(dis[v]>dis[u]+w) { dis[v]=dis[u]+w; if(!inQ[v]) { inQ[v]=true; Q.push(v); } } } } return dis[des]!=inf?dis[des]:-1; } char o; int n,m,k,u,v,w; int main() { while(scanf("%d %d %d",&n,&m,&k)!=EOF) { clear(); for(int i=0;n>i;i++) { UFS[i]=i; } for(int i=0;m>i;i++) { scanf("%d %d %d",&u,&v,&w); add(u,v,w); } for(int i=0;k>i;i++) { scanf("\n%c %d %d",&o,&u,&v); if(o=='I') Union(u,v); else printf("%d\n",SPFA(u,v,n)); } } return 0; }
G.二分枚举构图+二分匹配。该题想说明的就是A中的一个城市只能够对应B中的一个城市。先通过floyd求出任意城市之间的最短路,然后二分枚举构好A->B城市的二分图,然后使用匈牙利算法计算是否能够达到完全匹配。
#include <cstdio> #include <cstring> #include <iostream> #include <algorithm> #include <ctime> #include <cassert> using namespace std; const int MAX = 105; const int INF = 0x3f3f3f3f; int n, N, M, reca[MAX], recb[MAX]; int mp[MAX][MAX]; int marry[MAX]; char G[MAX][MAX]; char vis[MAX]; void floyd() { for (int k = 0; k < n; ++k) { for (int i = 0; i < n; ++i) { if (mp[i][k] == INF || i == k) continue; for (int j = 0; j < n; ++j) { if (mp[k][j] == INF || j == k) continue; if (mp[i][j] > mp[i][k] + mp[k][j]) { mp[i][j] = mp[i][k] + mp[k][j]; } } } } } bool path(int u) { for (int i = 0; i < M; ++i) { if (!G[u][i] || vis[i]) continue; vis[i] = 1; if (marry[i] == -1 || path(marry[i])) { marry[i] = u; return true; } } return false; } void build(int lim) { memset(G, 0, sizeof (G)); for (int i = 0; i < N; ++i) { for (int j = 0; j < M; ++j) { if (mp[reca[i]][recb[j]] <= lim) { G[i][j] = 1; } } } } bool Ac() { int cnt = 0; memset(marry, 0xff, sizeof (marry)); for (int i = 0; i < N; ++i) { memset(vis, 0, sizeof (vis)); if (path(i)) { ++cnt; } } return cnt == M; } int bsearch(int l, int r) { int mid, ret; while (l <= r) { mid = (l + r) >> 1; build(mid); if (Ac()) { ret = mid; r = mid - 1; } else { l = mid + 1; } } return ret; } int main() { while (scanf("%d", &n) == 1) { for (int i = 0; i < n; ++i) { for (int j = 0; j < n; ++j) { scanf("%d", &mp[i][j]); } } scanf("%d", &N); for (int i = 0; i < N; ++i) { scanf("%d", &reca[i]); --reca[i]; } scanf("%d", &M); for (int i = 0; i < M; ++i) { scanf("%d", &recb[i]); --recb[i]; } floyd(); printf("%d\n", bsearch(0, 10000)); } return 0; }
H.
法一:通过题目给定的关系不难知道a[i] = 3*a[i-1] + a[i-2] - 3*a[i-3]。假设要求第D天的口令,那么计算出a[D-1], a[D-2], a[D-3]就能够计算出a[D],求出四个系数后,代入X便可求解答案了。这题D给的数据比较大,直接计算肯定会TLE,那么使用矩阵并加上快速降幂来计算就简单多了,将给定的b,c,d作为3*1的矩阵B{a[0], a[-1], a[-2]}^-1,矩阵A=
3 1 -3
1 0 0
0 1 0
作为递推矩阵,那么在B矩阵左边乘上D-1个A最后的结果就是{a[D-1], a[D-2], a[D-3]}^-1,D-1个A相乘就可以使用矩阵快速幂了。
#include <iostream> #include <cstdlib> #include <cstdio> #include <cstring> #include <algorithm> #include <cassert> using namespace std; typedef long long int64; const int MOD = int(1e9)+7; int a, b, c, d, D, X; int64 R[3][3] = { {3, 1, -3}, {1, 0, 0}, {0, 1, 0} }; struct Matrix { int64 mat[3][3]; Matrix() { memset(mat, 0, sizeof (mat)); } void unit() { memset(mat, 0, sizeof (mat)); for (int i = 0; i < 3; ++i) { mat[i][i] = 1; } } void init() { memcpy(mat, R, sizeof (mat)); } void show() const; friend Matrix operator * (const Matrix & a, const Matrix & b); friend Matrix pow(Matrix a, int b); }; void Matrix::show() const { for (int i = 0; i < 3; ++i) { for (int j = 0; j < 3; ++j) { printf("%d ", mat[i][j]); } puts(""); } } Matrix operator * (const Matrix & a, const Matrix & b) { Matrix ret; for (int i = 0; i < 3; ++i) { for (int j = 0; j < 3; ++j) { for (int k = 0; k < 3; ++k) { ret.mat[i][j] += (a.mat[i][k] * b.mat[k][j]) % MOD; ret.mat[i][j] %= MOD; } } } return ret; } Matrix pow(Matrix a, int b) { Matrix ret; ret.unit(); while (b) { if (b & 1) { ret = ret * a; } a = a * a; b >>= 1; } return ret; } int64 cal(int64 na, int64 nb, int64 nc, int64 nd) { int64 ini[4] = {1}; for (int i = 1; i < 4; ++i) { ini[i] = (ini[i-1] * X) % MOD; } return ((na*ini[3])%MOD+(nb*ini[2])%MOD+(nc*ini[1])%MOD+(nd*ini[0])%MOD)%MOD; } void solve() { Matrix mm; mm.init(); mm = pow(mm, D-1); int64 na, nb, nc, nd; nb = ((mm.mat[0][0]*b)%MOD + (mm.mat[0][1]*c)%MOD + (mm.mat[0][2]*d)%MOD)%MOD; nc = ((mm.mat[1][0]*b)%MOD + (mm.mat[1][1]*c)%MOD + (mm.mat[1][2]*d)%MOD)%MOD; nd = ((mm.mat[2][0]*b)%MOD + (mm.mat[2][1]*c)%MOD + (mm.mat[2][2]*d)%MOD)%MOD; na = (3*nb)%MOD+nc-(3*nd)%MOD; printf("%d\n", int((cal(na, nb, nc, nd)+MOD)%MOD)); } int main() { while (scanf("%d %d %d %d", &a, &b, &c, &d) != EOF) { assert(a >= 0 && a <= 100); scanf("%d %d", &D, &X); solve(); } return 0; }
法二:根据表达式a[i] = 3*a[i-1] + a[i-2] - 3*a[i-3],由于该表达式为常系数3阶齐次递推关系,因此设a[n] = r^n,代入方程求出特征根为-1,1,3,因此a[n]=A*(-1)^n+B*(1)^n+C*(3)^n,令a[0]=d,a[1]=c,a[2]=b求出A,B,C之后再根据通项公式求出D天后的系数。b,c,d是8的倍数保证了解三元一次方程组时不会出现分数。
#include <iostream> #include <cstdlib> #include <cstring> #include <cstdio> using namespace std; typedef long long int64; const int64 MOD = (int64)1e9 + 7; int64 a, b, c, d, D, X; int64 _pow(int64 a, int64 b) { int64 ret = 1; while (b) { if (b & 1) { ret *= a; ret %= MOD; } b >>= 1; a *= a; a %= MOD; } return ret; } int64 cal(int64 na, int64 nb, int64 nc, int64 nd) { int64 ini[4] = {1}; for (int i = 1; i < 4; ++i) { ini[i] = (ini[i-1] * X) % MOD; } return ((na*ini[3])%MOD+(nb*ini[2])%MOD+(nc*ini[1])%MOD+(nd*ini[0])%MOD)%MOD; } int main() { while (scanf("%lld %lld %lld %lld", &a, &b, &c, &d) != EOF) { scanf("%lld %lld", &D, &X); int64 A = (3*d-4*c+b)/8; int64 B = (3*d+2*c-b)/4; int64 C = (b-d)/8; a = (A * ((D+2) & 1 ? -1 : 1) + B + C * _pow(3, D+2)) % MOD; b = (A * ((D+1) & 1 ? -1 : 1) + B + C * _pow(3, D+1)) % MOD; c = (A * ((D) & 1 ? -1 : 1) + B + C * _pow(3, D)) % MOD; d = (A * ((D-1) & 1 ? -1 : 1) + B + C * _pow(3, D-1)) % MOD; printf("%lld\n", (cal(a, b, c, d)+MOD)%MOD); } return 0; }
PS:感谢 “|wo只洗碗、不吃fan” 对H题提供新的解题思路。