A. Dalton the Teacher

题意

nn 个数 a1,a2,,ana_1,a_2,\ldots,a_n
现在每次操作可以交换两个数,请求出最少的操作数,使得 1in,aii\forall 1\le i \le n, a_i\ne i

解法

设当前满足 ai=ia_i=i 的数数量为 xx
可以证明,答案为 x+12\big \lfloor \dfrac{x+1}{2} \big \rfloor

证明:
必要性:一次操作只能调换两个数的位置,至少需要 x+12\big \lfloor \dfrac{x+1}{2} \big \rfloor 次操作才能调换每一个数。
充分性:我们构造一个合法方案。如果 xx 是偶数,我们直接两两一换即可。如果 xx 是奇数,我们先将 x1x-1 两两一换,显然最后一个数一定能在前 x1x-1 个数中找到一个能和它换的。

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
#include<bits/stdc++.h>
using namespace std;

using ll=long long;
using ull=unsigned long long;
using pii=pair<int,int>;
#define all(x) x.begin(),x.end()
#define mem0(x) memset(x,0,sizeof(x))
#define YES puts("YES")
#define NO puts("NO")
#define Yes puts("Yes")
#define No puts("No")
#define errorf(...) fprintf(stderr, __VA_ARGS__)

int read(){
int f=1,x=0;char c=getchar();
while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
while(isdigit(c)){x=x*10+c-'0';c=getchar();}
return x*f;
}

const int N = 1e5+5;
void solve(int kase){
int n=read();
int cnt =0;
for(int i=1;i<=n;i++){
int x=read();
if(x==i)cnt++;
}
printf("%d\n",(cnt+1)/2);
}

int main(){
int T=read(),TT=1;
while(T--){
solve(TT++);
}
return 0;
}

B. Longest Divisors Interval

题意

给定 n (n1018)n\ (n \le 10^{18}),求出最长区间 [l,r][l,r],使得 lir\forall l\le i\le rnnii 的倍数(我们定义区间 [l,r][l,r] 的长度为 rl+1r-l+1)。

解法

所有的合法最长区间中,一定有一个是从 11 开始的。
证明:
不妨设 [l,r][l,r] 是答案的合法最长区间。
\becausexx 个连续整数中有一个 xx 的倍数,
\therefore [l,r][l,r] 中包含 1,2,,rl+11,2,\ldots,r-l+1 的倍数。
\because lir\forall l\le i\le rnnii 的倍数,
\therefore nn1,2,,rl+11,2,\ldots,r-l+1 的倍数。
\therefore [1,rl+1][1,r-l+1] 是一个合法解,且长度与 [l,r][l,r] 相同。

因此只需要从 11 开始不断枚举 rr 即可。因为 lcm(1,2,,43)=9419588158802421600>1018\operatorname{lcm} (1,2,\ldots,43)=9419588158802421600>10^{18},所以最多枚举 4242 个数。

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
#include<bits/stdc++.h>
using namespace std;

using ll=long long;
using ull=unsigned long long;
using pii=pair<int,int>;
#define all(x) x.begin(),x.end()
#define mem0(x) memset(x,0,sizeof(x))
#define YES puts("YES")
#define NO puts("NO")
#define Yes puts("Yes")
#define No puts("No")
#define errorf(...) fprintf(stderr, __VA_ARGS__)

int read(){
int f=1,x=0;char c=getchar();
while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
while(isdigit(c)){x=x*10+c-'0';c=getchar();}
return x*f;
}

void solve(int kase){
ll n = read<ll>();

ll x = 1;
while(n%x==0)x++;

printf("%lld\n",x-1);
}

int main(){
int T=read(),TT=1;
while(T--){
solve(TT++);
}
return 0;
}

C2. Dual (Hard Version)

题意

给定 n (n20)n\ (n\le 20) 个数 a1,a2,,an (20ai20)a_1,a_2,\ldots,a_n\ (-20\le a_i\le 20)。现在可以进行一次操作,选定 i,ji,j(不一定不同),使得 aiai+aja_i\leftarrow a_i+a_j

要使这个序列单调不减,请构造出一个最多使用 3131 次的方案。

解法

注意到若这个序列全部非负,我们可以执行 ai+1ai+1+aia_{i+1}\leftarrow a_{i+1}+a_{i} 操作 n1n-1 次,使得整个序列单调不减。同理若这个序列全部非正,我们也可以执行 ai1ai1+aia_{i-1}\leftarrow a_{i-1}+a_{i} 操作 n1n-1 次,使得整个序列单调不减。

上面这个操作需要使用 1919 步,因此我们考虑如何在 1212 步内将这个序列全部变为同号。

现在序列中既有负数也有正数(如果没有,直接用上面的方法处理)。
不失一般性,我们令最小数的绝对值大于等于最大数的绝对值。考虑两种情况:

  • 正数的个数小于等于 1212 个,直接用最小数加给所有正数,在 1212 步内将序列变为全负。
  • 正数个数大于等于 1313 个,负数的个数小于等于 77 个。我们将任意一个正数用五次操作翻五番(aiai+aia_i\leftarrow a_i+a_i),这样这个数至少等于 3232,然后我们用这个翻五番的数加给每一个负数,这样就可以在 5+7=125+7=12 步操作将所有数变为正数。

若最小数的绝对值小于最大数的绝对值,同理。

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
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
#include<bits/stdc++.h>
using namespace std;

using ll=long long;
using ull=unsigned long long;
using pii=pair<int,int>;
#define all(x) x.begin(),x.end()
#define mem0(x) memset(x,0,sizeof(x))
#define YES puts("YES")
#define NO puts("NO")
#define Yes puts("Yes")
#define No puts("No")
#define errorf(...) fprintf(stderr, __VA_ARGS__)

int read(){
int f=1,x=0;char c=getchar();
while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
while(isdigit(c)){x=x*10+c-'0';c=getchar();}
return x*f;
}

const int N = 25;
int a[N];
void solve(int kase){
int n=read();
vector<pii> v;
for(int i=1;i<=n;i++){
a[i]=read();
v.push_back({a[i],i});
}
sort(all(v));

int cntneg=0,cntpos=0;

for(int i=0;i<n;i++){
if(v[i].first<0)cntneg++;
if(v[i].first>0)cntpos++;
}

int posmax = max_element(a+1,a+1+n)-a;
int posmin = min_element(a+1,a+1+n)-a;

if(cntpos==0){
printf("%d\n",n-1);
for(int i=n;i>1;i--){
printf("%d %d\n",i-1,i);
}
return;
}
if(cntneg==0){
printf("%d\n",n-1);
for(int i=1;i<n;i++){
printf("%d %d\n",i+1,i);
}
return;
}

vector<pii> ans;
if(abs(a[posmin])>=abs(a[posmax])){
if(cntpos<=12){
for(int i=0;i<n;i++){
if(v[i].first>0){
ans.push_back({v[i].second,posmin});
}
}

for(int i=n;i>1;i--){
ans.push_back({i-1,i});
}
}
else{
for(int i=0;i<5;i++){
ans.push_back({posmax,posmax});
}
for(int i=0;i<n;i++){
if(v[i].first<0){
ans.push_back({v[i].second,posmax});
}
}
for(int i=1;i<n;i++){
ans.push_back({i+1,i});
}
}
}
else{
if(cntneg<=12){
// printf("cntneg = %d\n",cntneg);
for(int i=0;i<n;i++){
if(v[i].first<0){
ans.push_back({v[i].second,posmax});
}
}

for(int i=1;i<n;i++){
ans.push_back({i+1,i});
}
}
else{
for(int i=0;i<5;i++){
ans.push_back({posmin,posmin});
}
for(int i=0;i<n;i++){
if(v[i].first>0){
ans.push_back({v[i].second,posmin});
}
}
for(int i=n;i>1;i--){
ans.push_back({i-1,i});
}
}
}


cout<<ans.size()<<'\n';
for(auto x:ans){
cout<<x.first<<' '<<x.second<<'\n';
a[x.first]+=a[x.second];
}

assert(is_sorted(a+1,a+1+n));
}

int main(){
int T=read(),TT=1;
while(T--){
solve(TT++);
}
return 0;
}

D. Earn or Unlock

题意

n (n105)n\ (n\le10^5) 张牌,权值为 v1vnv_1\sim v_n。现在只有第一张牌解锁了,后面的牌全部是锁住的。
现在你可以进行操作:选定一张解锁了的牌 ii

  • 再解锁后面 viv_i 张牌;
  • 答案加上 viv_i

求答案最大值。

解法

注意到一个事实:若总共恰好解锁了 xx 张牌,则答案为 v1+v2++vmax(n,x)(x1)v_1+v_2+\ldots+v_{\max(n,x)}-(x-1)

dpi,jdp_{i,j} 代表能否使用前 ii 张牌恰好解锁 jj 张牌。

初始条件:dp0,1=1dp_{0,1}=1
转移:

dpi,j=max(dpi1,j,dpi1,jvi)dp_{i,j}=\max(dp_{i-1,j},dp_{i-1,j-v_i})

事实上,第二个转移 dpi,j=dpi1,jvidp_{i,j}=dp_{i-1,j-v_i} 当且仅当 jviij-v_i\ge i 时才能成功转移,为此我们在每次操作后,都将 dpi,idp_{i,i}00,保证都是从合法的状态转移的。

对于 jj 只需要算到 2n2n 就够了,显然解锁超过 2n2n 张牌是更劣的。

bitset 优化,复杂度为 O(n2w)O(\frac{n^2}{w})勉强够。

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
#include<bits/stdc++.h>
using namespace std;

using ll=long long;
using ull=unsigned long long;
using pii=pair<int,int>;
#define all(x) x.begin(),x.end()
#define mem0(x) memset(x,0,sizeof(x))
#define YES puts("YES")
#define NO puts("NO")
#define Yes puts("Yes")
#define No puts("No")
#define errorf(...) fprintf(stderr, __VA_ARGS__)

int read(){
int f=1,x=0;char c=getchar();
while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
while(isdigit(c)){x=x*10+c-'0';c=getchar();}
return x*f;
}

const int N = 2e5+5;
bitset<N> dp;
int a[N];
ll s[N];

int main(){
int n=read();
for(int i=1;i<=n;i++){
a[i]=read();
s[i]=s[i-1]+a[i];
}

dp[1]=1;
ll ans = 0;
for(int i=1;i<=n;i++){
dp=dp|dp<<a[i];
if(dp[i]){
ans = max(ans, s[i]-i+1);
}
dp[i]=0; // 保证是从有效的状态转移的
}
for(int i=n+1;i<=n*2;i++){
if(dp[i]){
ans = max(ans,s[n]-i+1);
break; // i 更大答案一定更劣,后面的就不算了
}
}

printf("%lld\n",ans);
return 0;
}