周赛(四) A/B Problem Description
Input
Output
Sample Input
Sample Output
参考AC代码 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
using namespace std;
void exgcd(int a,int b,int &x,int &y)
{
if ( b== 0 )
{
x= 1;
y= 0;
return ;
}
exgcd(b,a%b,x,y);
int t=x;
x=y;
y=t-a /b*y;
return ;
}
int main ()
{
int T;
cin>>T;
while (T--)
{
int p,q,y=9973;
long long B,n;
cin>>n>>B;
exgcd(y,B,p,q);
q*=n;
q=(q%y+y)%y;
cout<<q<<endl;
}
}
思路 exgcd模版题。 (n=A%9973)等价为A=9973X+n。y=(A/B)%9973等价为A/B=9973 Z+y 因为A不知道,所以要把A消掉。得到的方程整理即是9973(Z B-X) +By = n 求y。所以把(Z B-X)当成一个未知数就可以了,扩展欧几里德A掉。
注意事项 乍看和不定方程无关。但是经过最简单的数学化简后即可化成不定方程,带入exgcd模版后暴力A掉。
Tree Problem Description
Input
Output
Sample Input
Sample Output
题目要求 有N个点,若VA或VB或VA+VB是质数,那么点AB的边权是min(VA,VB,|VA-VB|),它们才可以连接。求连接所有点的边权之和,若不可连接所有的点输出-1.
参考AC代码 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
using namespace std;
int t,n;
int a[605];
int prime[1000005],p[605];
struct node
{
int u,v,w;
}e[180005];
bool cmp(node a,node b)
{
return a.w<b.w;
}
int find(int x)
{
return p[x]==x?x:find(p[x]);
}
int read ()
{
int x=0;
char ch=getchar();
while (ch<'0' ||ch>'9' )
ch=getchar();
while (ch>='0' &&ch<='9' )
{
x=x*10+ch-'0' ;
ch=getchar();
}
return x;
}
void setprime ()
{
memset(prime,-1,sizeof(prime));
prime[0]=prime[1]=0;
for (int i=2;i<1000000;i++)
if (prime[i]==-1)
for (int temp=2*i;temp<=1000000;temp+=i)
prime[temp]=0;
}
void kruskal(int top)
{
int k=0,ans=0,x,y;
for (int i=1;i<=n;i++)
p[i]=i;
for (int i=1;i<=top;i++)
{
if (k==n-1)
break ;
x=find(e[i].u),y=find(e[i].v);
if (x!=y)
{
ans+=e[i].w;
p[x]=y;
k++;
}
}
printf ("%d\n" ,k<n-1?-1:ans);
}
int main ()
{
t=read ();
setprime();
while (t--)
{
n=read ();
int top=0;
for (int i=1;i<=n;i++)
a[i]=read ();
for (int i=1;i<=n;i++)
for (int j=i+1;j<=n;j++)
if (prime[a[i]] || prime[a[j]] || prime[a[i]+a[j]])
{
e[++top].w=min(min(a[i],a[j]),abs(a[i]-a [j]));
e[top].u=i;
e[top].v=j;
}
sort(e+1,e+1+top,cmp);
kruskal(top);
}
return 0;
}
思路 最小生成树即可AC。 保存边集的点和权值后使用kruskal算法即可。素数的判断使用了数组,数组的索引就是范围内的所有整数,若为素数值为1,否则为0.
注意事项 注意k=n-1时退出循环,输出是判断k是否等于k-1相当于连接了所有点,不满足输出-1.
Max Sum of Max-K-sub-sequence Problem Description
Input
Output
Sample Input
Sample Output
题目要求 有一个“环状”数组,求最大长度不超过k的连续子串和的最大值,并输出起始和终止位置。
参考AC代码 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
using namespace std;
const int N = 1e5 + 10;
const int INF = 1e9;
int a[N],sum[N << 1];
struct node{
int x,id;
}q[N << 1];
int n,k;
void dull ()
{
int f = 0,r = 0;
int ans = -INF,s,e;
for (int i = 1; i <= (n << 1); ++i)
{
while (f < r && q[r - 1].x < sum[i])
--r;
q[r].x = sum[i],q[r++].id = i;
while (f < r && i - q[f].id >= k)
++f;
if (i >= k && q[f].x - sum[i - k] > ans)
{
ans = q[f].x - sum[i - k];
s = i - k + 1;
e = q[f].id;
}
}
printf ("%d %d %d\n" ,ans,(s > n ? s - n : s),(e > n ? e - n : e));
}
int main (){
int T;
scanf("%d" ,&T);
while (T--){
scanf("%d%d" ,&n,&k);
sum[0] = 0,sum[n + 1] = 0;
for (int i = 1; i <= n; ++i){
scanf("%d" ,&a[i]);
sum[i] = sum[i-1] + a[i];
}
for (int i = n + 1; i <= (n << 1); ++i) sum[i] = sum[i-1] + a[i - n];
dull();
}
return 0;
}
思路 单调队列即可AC。 代码中为了解决环状问题数组扩充了两倍,但其实只需扩充k-1位即可。 求出前缀和数组sum后进行单调队列最关键的四部操作:(顺序可交换)(因为求最大值所以要保证队列递减) 1.队头出队:为了保证队列的递减性,我们在插入元素v的时候,要将队尾的元素和v比较,如果队尾的元素不大于v,则删除队尾的元素,然后继续将新的队尾的元素与v比较, 直到队尾的元素大于v,这个时候我们才将v插入到队尾。 2.队尾出队:由于我们只需要保存i的前k-1个元素中的最大值,所以当队首的元素的索引或下标小于i-k+1的时候,就说明队首的元素对于求f(i)已经没有意义了,因为它已 经不在窗里面了。所以当index[队首元素]<i-k+1时,将队首元素删除。 3.记录头/尾的位置 4.保存max:单调队列至i的时候,此时单调队列记录的是i-k+1到i中的sum的最大值,那么[i-k+1,i]这一段的最大sum值其实是q[f]-sum[i-k],其中q[f]是单调队列的队列首 元素,也就是最大值。然后用ans记录比较最大值即可。
注意事项 理解单调队列的出队是解题的关键。 附上单调队列的最简单应用:一个长度为k的窗在整数数列上移动,求窗里面所包含的数的最大值1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
using namespace std;
struct Node
{
int val;
int index;
};
void GetMax(int *numSequence,int len, int *result,int k)
{
Node *que = new Node[len];
int head = 0;
int end = 0;
for (int i=0;i<len;i++)
{
Node tmp;
tmp.val = numSequence[i];
tmp.index = i;
while (end!=0 && que[end].val<=numSequence[i])
--end;
++end;
que[end] = tmp;
while (end!=0 && que[head].index<i-k+1)
++head;
result[i] = que[head].val;
}
}
int main ()
{
int len, k;
cin>>len>>k;
int *numSequence = new int[len];
int *maxResult = new int[len];
for (int i=0;i<len;i++)
cin>>numSequence[i];
GetMax(numSequence,len,maxResult,k);
for (int i=k-1;i<len;i++)
cout<<i<<": " <<maxResult[i]<<endl;
return 0;
}
Candy Sharing Game Problem Description
Input
Output
Sample Input
Sample Output
题目要求 N个人围成一圈手拿偶数个糖果,每次下达指令后所有人把自己一半的糖果给自己右边的人,若自己手上的糖果为奇数则+1,求最后下达多少次指令后所有人的糖果数才相等, 并输出糖果数。
参考AC代码 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
using namespace std;
int f(int a[],int n)
{
for (int i=0;i<n-1;i++)
if (a[i]!=a[i+1])
return 1;
return 0;
}
int main ()
{
int n;
while (cin>>n&&n)
{
int a[500],flag=0;
for (int i=0;i<n;i++)
cin>>a[i];
while (f(a,n))
{
flag++;
int temp=a[n-1]/2;
for (int i=n-1;i>0;i--)
{
a[i]=a[i]/2+a[i-1]/2;
if (a[i]&1)
a[i]++;
}
a[0]=a[0]/2+temp;
if (a[0]&1)
a[0]++;
}
cout<<flag<<" " <<a[0]<<endl;
}
return 0;
}
思路 模拟题。 每个人在给出一半后才可以接受别人的糖果。for循环遍历后考虑头尾成环的特殊情况即可。
注意事项 环状拉成数组。
Tr A Problem Description
Input
Output
Sample Input
Sample Output
参考AC代码 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
typedef struct Node
{
int m[11][11];
}Matrix;
Matrix init, unit; //初始化输入矩阵,单位矩阵如果用递归写Pow函数可以不用单位矩阵
int n, K;
void Init( ) //初始化
{
scanf( "%d%d" , &n, &K );
for ( int i=0; i<n; ++ i )
for ( int j=0; j<n; ++ j )
{
scanf( "%d" , &init.m[i][j] );
unit.m[i][j]=( i == j );
}
}
Matrix Mul( Matrix a, Matrix b ) //矩阵乘法
{
Matrix c;
for ( int i=0; i<n; ++ i )
for ( int j=0; j<n; ++ j )
{
c.m[i][j]=0; //特别注意
for ( int k=0; k<n; ++ k )
c.m[i][j] += a.m[i][k]*b.m[k][j];
c.m[i][j] %= 9973;
}
return c;
}
Matrix Pow( Matrix a, Matrix b, int k )
{
while ( k>1 )
{
if ( k&1 ) // k为奇数时
{
k --;
b=Mul( a, b );
}
else // k为偶数
{
k >>= 1;
a=Mul( a, a );
}
}
a=Mul( a, b );
return a;
}
int main ( )
{
int T;
scanf( "%d" , &T );
while ( T -- )
{
Matrix x;
Init( );
x=Pow( init, unit, K );
int sum=0, i=0;
n--;
while ( n >= 0 )
{
sum += x.m[n][n];
sum%=9973;
n --;
}
printf ( "%d\n" , sum%9973 );
}
}
思路 由于矩阵乘法具有结合律,因此A^4 = A A A A = (A A) (A A) = A^2 A^2。我们可以得到这样的结论:当n为偶数时,A^n = A^(n/2) A^(n/2);当n为奇数时, A^n = A^(n/2) A^(n/2) A (其中n/2取整)。这就告诉我们,计算A^n也可以使用二分快速求幂的方法。例如,为了算出A^25的值,我们只需要递归地计算出A^12、 A^6、A^3的值即可。 递归实现POW函数1
2
3
4
5
6
7
8
9
10
11
Matrix POW( Matrix t,int k )
{
if ( k == 1 )
return t;
Matrix t1 = POW( t, k/2 );
t1 = t1*t1;
if ( k & 1 )
return t1 * t;
else
return t1;
}
递归的容易理解,但时间花费较多。所以很少用
注意事项 了解矩阵乘法的基本规则。
以下给出矩阵运算的模版:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
struct Matrix
{
int matrix[15][15];
};
int n; //维度,即矩阵A的行列数
int MOD=9973;//好多问题要求给出取余之后的数字
Matrix E;
void initE() //初始化单位阵
{
for (int i=0;i<15;i++)
E.matrix[i][i]=1;
}
Matrix operator*(Matrix a,Matrix b)
{
int i,j,k;
Matrix c;
for (i=0;i<n;i++)
{
for (j=0;j<n;j++)
{
c.matrix[i][j] = 0;
for (k=0;k<n;k++)
c.matrix[i][j]+=(a.matrix[i][k]*b.matrix[k][j]);
c.matrix[i][j]%=MOD;
}
}
return c;
}
Matrix operator+(Matrix a,Matrix b)
{
Matrix c;
int i,j;
for (i=0;i<n;i++)
{
for (j=0;j<n;j++)
c.matrix[i][j] = a.matrix[i][j]+b.matrix[i][j];
c.matrix[i][j]%=9973;
}
return c;
}
Matrix operator^(Matrix a,int x)
{
Matrix p = E,q = a;
while (x>=1)
{
if (x%2==1)
p = p*q;
x/=2;
q = q*q;
}
return p;
}