# 2016 华南师大ACM校赛 SCNUCPC 非官方题解

A. 树链剖分数据结构板题

B. The background of water problem

 1 #include <cstdio>
2 #include <string.h>
3 #include <algorithm>
4 #include <vector>
5 using namespace std;
6
7 int N,K,M,X;
8 int people[1005][11];
9 int cmpOrder[11];
10
11 struct CmpNode{
12     CmpNode(int x):id(x){}
13     int id;
14     bool operator < (const CmpNode &other) const
15     {
16         for(int i=0; i<K; i++)
17         {
18             if(people[this->id][cmpOrder[i]] > people[other.id][cmpOrder[i]])
19                 return true;
20             else if(people[this->id][cmpOrder[i]] < people[other.id][cmpOrder[i]])
21                 return false;
22         }
23         return this->id<other.id;
24     }
25 };
26
27 void solve(FILE *fin=stdin, FILE *fout=stdout)
28 {
29     int t;
30     fscanf(fin,"%d",&t);
31     while(t--)
32     {
33         fscanf(fin,"%d%d",&N,&K);
34         vector<CmpNode> nodes;
35         for(int i=0;i<N;i++)
36         {
37             nodes.push_back(CmpNode(i));
38             for(int j=1;j<=K;j++)
39                 fscanf(fin,"%d",&people[i][j]);
40         }
41         fscanf(fin,"%d",&M);
42         while(M--)
43         {
44             for(int i=0;i<K;i++)
45                 fscanf(fin,"%d",cmpOrder+i);
46             fscanf(fin,"%d", &X);
47             sort(nodes.begin(),nodes.end());
48             fprintf(fout,"%d\n",nodes[X-1].id+1);
49         }
50     }
51 }
52
53 int main()
54 {
55     solve(stdin,stdout);
56     return 0;
57 }
B. The background of water problem

 1 #include <stdio.h>
2 #include <algorithm>
3
4 int N, K, M, X;
5 int order[11];
6
7 struct stu {
8     int id, score[11];
9     bool operator <(const stu&x) const {
10         for(int i=1; i<=K; i++)
11             if(score[order[i]] != x.score[order[i]])
12                 return score[order[i]] > x.score[order[i]];
13         return id < x.id;
14     }
15 }student[1001];
16
17 int main() {
18     int T;
19     scanf("%d", &T);
20     while(T--) {
21         scanf("%d%d", &N, &K);
22         for(int i=1; i<=N; i++) {
23             student[i].id = i;
24             for(int j=1; j<=K; j++)
25                 scanf("%d", &student[i].score[j]);
26         }
27         scanf("%d", &M);
28         while(M--) {
29             for(int i=1; i<=K; i++)
30                 scanf("%d", order+i);
31             std::sort(student+1, student+1+N);
32             scanf("%d", &X);
33             printf("%d\n", student[X].id);
34         }
35     }
36     return 0;
37 }
B. The background of water problem

C. Oyk cut paper forever

永远的Oyk剪纸（大雾）。Oyk给面子Z大师，玩$C$轮剪纸，每轮给定一条长为$k$个单位的纸带，Z大师先手可以剪去（任意）$N_1$个单位，但不能不剪或全部拿走。此后每轮都只能剪$1$到$2\times N_1$个单位，能拿走最后一段纸带的人获胜，问Oyk第一次获胜是第几轮。

斐波那契博弈，证明参见：

原题参见 hdoj 2516. 取石子游戏 等。

首先知道了这是个斐波那契博弈，接下来我们要做的就是判断$K_i$是否为Fibonacci数。容易想到的是用递推打一个表，将Fibonacci数存起来或标记一下。但是我们知道斐波那契数列通项公式为$F_n=\frac1{\sqrt5}\left[\left(\frac{1+\sqrt5}2\right)^n-\left(\frac{1-\sqrt5}2\right)^n\right]$（比内公式），还知道判断一个数$x$是否为Fibonacci数只需判断$5x^2+4$或$5x^2-4$是否为完全平方数（参考：Wiki示例）（即判断开根号后是否为整数），于是Over。

 1 #include <cstdlib>
2 #include <cstdio>
3 #include <cmath>
4 using namespace std;
5
6 int main()
7 {
8     int T;
9     scanf("%d",&T);
10     while(T--){
11         int n,i;
12         int ans=0;
13         scanf("%d",&n);
14         for(i = 1;i<=n;i++){
15             int a;
16             scanf("%d",&a);
17             if(!ans&&(sqrt(5*a*a+4)-(int)sqrt(5*a*a+4)<1e-6||sqrt(5*a*a-4)-(int)sqrt(5*a*a-4)<1e-6)){
18                 ans=i;
19             }
20         }
21         if(ans)printf("%d\n",ans);
22         else puts("Oyk forever!");
23     }
24     return 0;
25 }
C. OykOyk!

 1 #include <stdio.h>
2 using namespace std;
3
4 int flag[100100];
5 void init() {
6     int a = 1, b = 2;
7     while(b<=100000) {
8         ++flag[b];
9         b += a;
10         a = b-a;
11     }
12 }
13 int main() {
14     int T; init();
15     scanf("%d", &T);
16     while(T--) {
17         int C, k, res=0;
18         scanf("%d", &C);
19         for(int i=1; i<=C; i++) {
20             scanf("%d", &k);
21             if(!res&&flag[k])
22                 res = i;
23         }
24         res?printf("%d\n", res):puts("Oyk forever!");
25     }
26     return 0;
27 }
C. Oyk forever!

D. 最小费用流

挖$n$天的宝藏，每天需要$R_i$只铲子，当天用完会坏掉。有$3$种方式准备铲子：从商店买，$p$元一只；找铁匠$A$修理，每只$f$元同时修$m$天；找铁匠$B$修理，每只$s$元同时修$t$天。问最小花费。

最小费用流，参见网络流24题之餐巾问题。

  1 #include <iostream>
2 #include<stdio.h>
3 #include<string.h>
4 #define INF 99999999
5 #define min(x,y) (x)>(y)?(y):(x)
6 #define abs(x) ((x)>0?(x):-(x))
7 #define E 50000
8 struct p
9 {
10     int v,next,k,t,cost;
11 }edge[200000];
13 int mark[1001],dis[1001],now[1001];
14 void addedge(int a,int b,int k,int cost)
15 {
16     edge[tot].v=b;
17     edge[tot].k=k;
18     edge[tot].cost=cost;
19     edge[tot].t=tot+1;
22
23     edge[tot].v=a;
24     edge[tot].k=0;
25     edge[tot].cost=-cost;
26     edge[tot].t=tot-1;
29 }
30 int spfa()
31 {
32     int i,top,tail,cur;
33     for(i=0;i<=T;i++)
34         dis[i]=INF,mark[i]=0;
35     top=tail=0;
36     pop[top++]=S;
37     dis[S]=0;
38     mark[S]=1;
39     while(tail!=top)
40     {
41         cur=pop[tail++];
42         tail%=50000;
43         mark[cur]=0;
45             if(edge[i].k>0&&dis[edge[i].v]>dis[cur]+edge[i].cost)
46             {
47                 dis[edge[i].v]=dis[cur]+edge[i].cost;
48                 pre[edge[i].v]=cur;
49                 pid[edge[i].v]=i;
50                 if(mark[edge[i].v]==0)
51                 {
52                     mark[edge[i].v]=1;
53                     pop[top++]=edge[i].v;
54                     top%=50000;
55                 }
56             }
57     }
58     return dis[T];
59 }
60 int mincost()
61 {
62     int i,flow,tmp,ans,maxflow=0;
63     ans=0;
64     while(1)
65     {
66         tmp=spfa();
67         if(tmp==INF) break;
68         flow=INF;
69         for(i=T;i!=S;i=pre[i])
70             if(edge[pid[i]].k<flow)
71                 flow=edge[pid[i]].k;
72         for(i=T;i!=S;i=pre[i])
73         {
74             edge[pid[i]].k-=flow;
75             edge[edge[pid[i]].t].k+=flow;
76         }
77         maxflow+=flow;
78         ans+=tmp*flow;
79     }
80     return ans;
81 }
82 int main()
83 {
84     freopen("in.txt","r",stdin);
85     freopen("out.txt","w",stdout);
86     int i,j,p,m1,f,m2,s,tmp;
87     while(scanf("%d%d%d%d%d%d",&n,&p,&m1,&f,&m2,&s)!=EOF)
88     {
89         memset(edge,0xff,sizeof(edge));
90         tot=n*2+3;
91         S=0;
92         T=n*2+1;
93         for(i=1;i<=n;i++)
94         {
95             scanf("%d",&tmp);
96             tmp++;
103         }
104         printf("%d\n",mincost());
105     }
106     return 0;
107 }
D. Dig the treasure

E. Wwj's work

 1 #include <stdio.h>
2 #include <string.h>
3 #include <algorithm>
4 using namespace std;
5
6 const int CHAR = 26;
7 const int MAXN = 2020;
8 struct SAM_Node {
9     SAM_Node *fa, *next[CHAR];
10     int len;
11     int id,pos;
12     SAM_Node() {}
13     SAM_Node(int _len) {
14         fa = 0;
15         len = _len;
16         memset(next,0,sizeof(next));
17     }
18 };
19 SAM_Node SAM_node[MAXN*2], *SAM_root, *SAM_last;
20 int SAM_size;
21 SAM_Node *newSAM_Node(int len) {
22     SAM_node[SAM_size] = SAM_Node(len);
23     SAM_node[SAM_size].id = SAM_size;
24     return &SAM_node[SAM_size++];
25 }
26 SAM_Node *newSAM_Node(SAM_Node *p) {
27     SAM_node[SAM_size] = *p;
28     SAM_node[SAM_size].id = SAM_size;
29     return &SAM_node[SAM_size++];
30 }
31 void SAM_init() {
32     SAM_size = 0;
33     SAM_root = SAM_last = newSAM_Node(0);
34     SAM_node[0].pos = 0;
35 }
36 void SAM_add(int x,int len) {
37     SAM_Node *p = SAM_last, *np = newSAM_Node(p->len+1);
38     np->pos = len;
39     SAM_last = np;
40     for(; p && !p->next[x]; p = p->fa)
41         p->next[x] = np;
42     if(!p) {
43         np->fa = SAM_root;
44         return;
45     }
46     SAM_Node *q = p->next[x];
47     if(q->len == p->len + 1) {
48         np->fa = q;
49         return;
50     }
51     SAM_Node *nq = newSAM_Node(q);
52     nq->len = p->len + 1;
53     q->fa = nq;
54     np->fa = nq;
55     for(; p && p->next[x] == q; p = p->fa)
56         p->next[x] = nq;
57 }
58
59 int sub[MAXN][MAXN];
60 char S[MAXN];
62     memset(sub, 0, sizeof(sub));
63     scanf("%s", S);
64     int len = strlen(S);
65     for(int i=0; i<len; i++) {
66         SAM_init();
67         for(int j=i; j<len; j++)
69         for(int j=1; j<SAM_size; j++)
70             sub[i][ SAM_node[j].pos+i-1 ]
71                 += SAM_node[j].len - SAM_node[j].fa->len;
72         for(int j=i+1; j<len; j++)
73             sub[i][j] += sub[i][j-1];
74     }
75 }
76 void work() {
77     int Q, l, r;
78     scanf("%d", &Q);
79     while(Q--) {
80         scanf("%d%d", &l, &r);
81         printf("%d\n", sub[l-1][r-1]);
82     }
83 }
84 int main() {
85     int T;
86     scanf("%d", &T);
87     while(T--) {
89         work();
90     }
91     return 0;
92 }
E. Wwj's work

F. 防AK题，dfs+高斯消元

G. Not Easy Math Problem

递推法

先观察前面若干行若干列，发现各项系数构成杨辉三角。代几个数进去发现 $\displaystyle F(m,n)=B*\sum_{i=0}^{m-1}C_{m-i+n-2}^{n-1}*2^i$。

$O(M)$肯定会TLE啊，算算算 $\displaystyle \frac{2F(m,n)-F(m,n)}{B}=C_{n-1}^{n-1}*2^m-C_{m+n-2}^{n-1}*2^0+\sum_{i=1}^{m-1}\left(C_{m-i+n-1}^{n-1}-C_{m-i+n-2}^{n-1}\right)*2^i$

发现可以利用组合数性质，令$\displaystyle T(n-1)=\sum_{i=0}^{m-1}C_{m-i+n-2}^{n-1}*2^i$，$\displaystyle T(n-2)=\sum_{i=0}^{m-1}C_{m-i+n-2}^{n-2}*2^i$

则$\displaystyle T(n-1)=C_{n-1}^{n-1}*2^m-C_{m+n-2}^{n-1}+T(n-2)-C_{m+n-2}^{n-2}$

递推下去得$\displaystyle T(n-1)=2^{n-1}*2^m-2*\sum_{i=0}^{n-2}C_{m+n-2}^i-C_{m+n-2}^{n-1}$

来个快速幂，再预处理下逆元和组合数，$O(log(M+N)+N)$跑得飞快（$log$里面的$N$可以在前面预处理组合数的循环去掉，丑就是了）。

数学归纳法

参见 hdoj. 5490 题解。归纳公式为$\displaystyle F(m,n)=\frac{q*F(m,n-1)-B*C_{m+n-2}^{n-1}}{q-1}$，然后递推，时间复杂度$O(log(M)+N)$（如果用快速幂算$2^{m-1}$的话）。

 1 #include <stdio.h>
2 const int MOD = 100000007;
3 inline int add(int a, int b) { return (a%MOD+b%MOD)%MOD; }
4 inline int sub(int a, int b) { return ((a-b)%MOD+MOD)%MOD; }
5 inline int mul(int a, int b) { return int((long long)a%MOD*(b%MOD)%MOD); }
6 inline int pow(int x, int n) {
7     int res = 1;
8     while(n) {
9         if(n&1) res = mul(res, x);
10         x = mul(x, x);
11         n >>= 1;
12     }
13     return res;
14 }
15 int inv[1001]={1,1};
16 void init() {
17     for(int i=2; i<=1000; i++)
18         inv[i] = mul(inv[MOD%i], MOD-MOD/i);
19 }
20 int B, M, N;
22     scanf("%d%d%d", &B, &M, &N);
23 }
24 int Binomial[1001]={1};
25 void work() {
26     for(int i=1; i<N; i++)
27         Binomial[i] = mul(mul(Binomial[i-1], M+N-i-1), inv[i]);
28     int res = pow(2, M+N-1);
29     for(int i=0; i<N; i++)
30         res = sub(res, mul(Binomial[i], 2));
31     res = mul(add(res, Binomial[N-1]), B);
32     printf("%d\n", res);
33 }
34 int main() {
35     int T; init();
36     scanf("%d", &T);
37     while(T--) {
39         work();
40     }
41     return 0;
42 }

H. Party!

Wwj和他的女朋友们总共$N$个人去开趴体。每个人如果还没把自己的礼物送出去的话，就可以从别人手中收到礼物。第$i$个人得到第$j$人礼物的同时，也会得到一个快乐指数$H_{i,\ j}$。求所有人的快乐指数的总和的最大值。

诶？有人用贪心做？有人用搜索做？听说还有人用最小生成树做？诶等等那个用无向图MST的什么心态？这不是有向图？（我的天呐.jpg）

这题可以跑一个网络流，当然也可以DP。谁告诉你给一个矩阵就一定是图论题了？mdzz。

能DP我当然不写网络流，考虑收礼物状态矩阵$M$，$M_{i,\ j}$代表第$i$个人收没收第$j$人的礼物（$i\neq j$），收了为$1$，没收为$0$。由于每个人只有一份礼物，他只能送给一个人，所以在同一列内最多只有$1$个$1$，所以状态矩阵$M$可以直接拍扁。好的，$N\leq 10$，我们可以愉快地状态压缩了，状态数$2^N-1$，dp时间复杂度$O(2^N N^2)$。

考虑任意状态state，如果第$i\geq 0$位为$1$（即$state\&(1<<i)!=0$），则表明该状态下第$i$个人已经把他的礼物给出去了（当然也不可能发生给自己的情况）。对每一个状态求最大快乐指数，再取所有状态的最大值。

 1 #include <stdio.h>
2
3 int N, H[11][11];
4 inline void getMax(int&n, int x) { if(n<x) n=x; }
6     scanf("%d", &N);
7     for(int i=0; i<N; i++)
8         for(int j=0; j<N; j++)
9             scanf("%d", H[i]+j);
10 }
11 void work() {
12     int maxState = 1<<N, dp[maxState]={0};
13     for(int state=0; state<maxState; state++)
14         for(int i=0; i<N; i++) if(!(state&(1<<i)))
15             for(int j=0; j<N; j++) if(i!=j&&!(state&(1<<j)))
16                 getMax(dp[state+(1<<j)], dp[state]+H[i][j]);
17     int res = 0;
18     for(int state=0; state<maxState; state++)
19         getMax(res, dp[state]);
20     printf("%d\n", res);
21 }
22 int main() {
23     int T;
24     scanf("%d",&T);
25     while(T--) {
27         work();
28     }
29     return 0;
30 }
H. Party!

I. Square

 1 #include <stdio.h>
2 #define ULL unsigned long long
3 int main() {
4     ULL res;
5     int n;
6     int T;
7     scanf("%d",&T);
8     while(T--) {
9         scanf("%d",&n);
10         res=1;
11         for(ULL i = 0; i<(ULL)(n-1)*(n-1)/47; i++)
12             res=(res<<47)%100007;
13         for(ULL i = 0; i<(ULL)(n-1)*(n-1)%47; i++)
14             res=(res*2)%100007;
15         printf("%d\n",res);
16     }
17     return 0;
18 }
I. Square

 1 #include <stdio.h>
2 const int MOD = 100007;
3 long long pow(long long x, int n) {
4     long long res = 1LL;
5     while(n) {
6         if(n&1) res = res*x%MOD;
7         x = x*x%MOD;
8         n >>= 1;
9     }
10     return res;
11 }
12 int main() {
13     int T, N;
14     scanf("%d",&T);
15     while(T--) {
16         scanf("%d",&N);
17         --N; N *= N;
18         printf("%d\n", pow(2, N));
19     }
20     return 0;
21 }
I. Square

J. Rotate and skew

windows系统里面有个“画图”工具，相信大家一定不会陌生。但里面没有旋转任意$x$角度的功能，只有“扭曲”的功能。如逆时针旋转$28^\circ$，我们发现可以先对$x$轴扭曲$-14^\circ$，再对$y$轴扭曲$25^\circ$，再对$x$轴扭曲$-14^\circ$，就成功辣！问给定角度$x$，输出三次扭曲的角度。

好多同学可能一开始先取个基向量，比如$\vec b=(0,1)$，然后想$x$轴扭曲$-14^\circ$应该是$\vec{b'}=(tan14^\circ,1)$，$y$轴再扭曲$25^\circ$应该是$\vec{b''}=(tan14^\circ, 1-tan25^\circ)$，再扭曲一下……再$arc tan$一下……咦？怎么出来的不是$28^\circ$了？

Naive。如果是这样那还叫扭曲吗？那叫拉伸！你倒是把$x$乘进去啊！把$y$乘进去啊！

• 首先考虑基向量$\vec a=(1,0)$。设旋转角度$\theta$。先水平扭曲任意角度，不变。垂直扭曲$\varphi$，$\vec{a'}=(1, tan\varphi)$，再水平扭曲一次得$\vec{a''}=(1-tan(x), tan\varphi)$，和$tan\theta$解一下发现$x=\frac\theta2$，于是知道了水平扭曲角度。
• 正解(1)：考虑向量$\vec x=\begin{bmatrix}x\\y\end{bmatrix}$，水平扭曲矩阵$A=\begin{bmatrix}1&tan(-\frac\theta2)\\0&1\end{bmatrix}$，　（$A\vec x=\begin{bmatrix}x+ytan(-\frac\theta2)\\y\end{bmatrix}$，看不懂的学线代去）
• 三个扭曲矩阵相乘应该是$M=ABA$，其中我们要求的是中间的垂直扭曲矩阵$B$。
• 已知旋转矩阵$M=\begin{bmatrix}cos\theta&-sin\theta\\sin\theta&cos\theta\end{bmatrix}$，解得$B=\begin{bmatrix}1&0\\sin\theta&1\end{bmatrix}$。
• 但是我们的垂直扭曲矩阵应该要长成$B=\begin{bmatrix}1&0\\tan\varphi&1\end{bmatrix}$的样子。
• 所以我们需要用$tan$正切值去模拟$sin$正弦值（本来就是要求用扭曲模拟旋转）。
• 正解(2)：再考虑基向量$\vec b=(0,1)$。
• 联立化简 \left\{\begin{matrix}\begin{aligned}&x_1=x_0+y_0*tan(-\frac\theta2)\\&y_1=y_0+x_1*tan(\varphi)\\&x_2=x_1+y_1*tan(-\frac\theta2)\end{aligned}\end{matrix}\right.，\left\{\begin{matrix}\begin{aligned}&x_0=0,\ y_0=1\\&tan(-\theta)=\frac{x_2}{y_1}\end{aligned}\end{matrix}\right.  得  $\displaystyle\frac{tan(\frac\theta2)[sec(\theta)tan(\varphi)-tan(\theta)]}{tan(\frac\theta2)tan(\varphi)-1}=0$
• 解 $sec(\theta)tan(\varphi)=tan(\theta)$ 得垂直扭曲角度 $\varphi=tan^{-1}sin(\theta)$
• 啥？你还要$+k\pi$？你还要分母不为$0$？$\theta$不为$0$？这都要我解，你咋不上天呢。
• 发现题目对精度要求不高，反正切取个整即可。也可以直接打个垂直扭曲角度的表。

 1 #include <stdio.h>
2 #include <math.h>
3 const double PI = acos(-1.L);
4 int main() {
5     double x = 0.0, y = 1.0;
6     x = x + tan(-14.*PI/180.) * y;
7     y = y + tan(25.*PI/180.) * x;///注意这里是tan25模拟sin28，若这里直接用sin28则最后atan回来的结果是28.0整
8     x = x + tan(-14.*PI/180.) * y;
9     printf("%f\n", atan(x/y)*180./PI);
10     return 0;
11 }

1 // http://scarky.com/widget/getiframe/PLNRB5QG/
2 #include <stdio.h>
3 int y[]={0,2,4,6,8,10,12,14,15,17,19,21,22,24,25,27,28,29,30,32,33,34,35};
4 int main(int i) {
5   while(~scanf("%d", &i))
6     i/=2, printf("%d %d %d\n", -i, i>0?y[i]:-y[-i], -i);
7   return 0;
8 }

1 #include <stdio.h>
2 #include <math.h>
3 const double PI = acos(-1.L);
4 int main() {
5     int x;
6     while(~scanf("%d", &x))
7         printf("%d %.f %d\n", -x/2, round(atan(sin(x*PI/180.))*180./PI), -x/2);
8     return 0;
9 }

——原创by BlackStorm，转载请注明出处。

posted @ 2016-04-12 17:53 BlackStorm 阅读(...) 评论(...) 编辑 收藏