LCX的算法笔记Week-1

详细记录一下一周里做的比较有意思的几道题

题目来源:字节跳动网络赛

因为少写了个括号导致WA啦😩😩😩,以为是算法有问题,就让队友写并查集解法啦。导致浪费了太多时间。又间接导致了下面要说的题没时间加特判了,寄。看来还是要多加练习。这道题除了特判比较繁琐,其他的还行。很容易就能想到要想使交换的次数最少,只需要先将给定的无序序列排列成有序序列,然后从第一个数字往后遍历,遍历的过程中,若当前有序序列中数字所在的位置和之前不同,则交换一次,并把记录的交换次数加1。比如样例1的输入 2 4 5 1 3 ,先排成1 2 3 4 5。遍历当前有序序列,发现1原本在下标4的位置,这时候就把下标4位置上的数1和下标1上的数2交换,依次类推,可算出总共交换了3次。样例1的k为0,所以没有预先交换的操作,最大值最小值相等,即3。所以就需要记录下之前无序序列每个数的下标,因为数的值可能会到10的九次方,如果用数组来维护这一信息,肯定会爆掉,且全局变量的数组也最多开到十的六次方的级别,所以用哈希表来存储,通过数据直接访问下标。再多开个数组记下原本的无序序列。所以很容易就能求得最小次数啦。关键在于最大次数的讨论,想要后面换的次数最多,就要使序列越无序越好,最无序的状态就是要n-1次交换才能变有序。假设n个数的无序序列,需要cnt次交换使序列变得有序,那么就需要(n-1)-cnt次使它最无序,这是我在比赛的时候提出的一个假设,当时自己写了几个序列,发现确实是这样的,后面队友写的并查集的解法也从侧面证明了这一点。完整代码如下

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
#include <bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int n,k;
int a[N],b[N];
unordered_map<int, int> dic;
int main(){
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
cin>>n>>k;
for (int i=1; i<=n; i++) {
cin>>a[i];
dic[a[i]]=i;
b[i]=a[i];
}
sort(a+1,a+1+n);
int cnt=0;
for (int i=1; i<=n; i++) {
if (dic[a[i]]!=i) {
// cout<<dic[a[i]]<<' '<<i<<endl;
int temp=dic[a[i]];
dic[a[i]]=i;
dic[b[i]]=temp;
int temp2=b[i];
b[i]=b[temp];
b[temp]=temp2;
cnt++;
}
}
int uncnt=n-1-cnt;
int max=0,min=0;
if (k>=cnt) {
if ((k-cnt)%2==0) {
min=0;
}
else min=1;
}
else min=cnt-k;
if (k>=uncnt) {
if ((k-uncnt)%2==0) {
max=n-1;
}
else max=n-2;
}
else max=n-1-(uncnt-k);
cout<<min<<' '<<max;
return 0;
}

队友的并查集解法
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
#include <bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int n,k;
int a[N],b[N];
int p[N], si[N];
bool st[N];
int cnt,num;
int find(int x)
{
if (p[x] != x) p[x] = find(p[x]);
return p[x];
}
void un(int a,int b){
int aa=find(a);
int bb=find(b);
if(aa!=bb){
p[aa]=bb;
si[bb]+=si[aa];
}
}
int main(){

ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin>>n>>k;
for(int i=1;i<=n;i++)
cin>>a[i];
for (int i = 1; i <= n; i ++ )
{
p[i] = i;
si[i] = 1;
}
for(int i=1;i<n;i++){
un(i,a[i]);
}

for(int i=1;i<=n;i++){
if(!st[find(i)]){
cnt+=si[find(i)]-1;
st[find(i)]=1;
num++;
}
}

if(k<=cnt) cout<<cnt-k<<' ';
else{
if((k-cnt)%2) cout<<1<<' ';
else cout<<0<<' ';
}
if(k<num) cout<<cnt+k;
else{
if((k-num)%2) cout<<n-1;
else cout<<n-2;
}
cout<<endl<<num<<' '<<cnt;
return 0;
}

题目来源:字节跳动网络赛

这道题本来是开局队友在看的,后来他们感觉太繁琐了,就放弃了。我在后期就又去看了看这道题,想到了怎么写,不过也非常繁琐,怒敲一百多行代码,敲那么多也敲出了一些bug,自增写成了自减,又是一个寄。等到bug找出来之后,样例都过了,就直接进行一个代码的提交,然后就WA啦。此时距离结束已经就剩两分钟,想了一会发现是对非法情况的特判不够全面,想出来之后也没时间提交了。希望有生之年能在算法竞赛上完成一次压哨绝杀。题意就是让输出n个数字的序列,数的值1-n里随意排列。且该序列的极大值点和极小值点满足题目的要求,对于极大值数量大于极小值数量的,可以先取出1和2作为两个端点,然后将n作为第一个极大值点,n-1作为第二个极大值点,直到满足数量要求,同样的,3作为第一个极小值,4作为第二个,以此类推,最后把没有用到的数全部放在1-n之间输出。输出的时候注意顺序即可。对于极大值数量小于极小值数量的,思想类似,反着来就行了。极大值极小值数量相等的,只要让第一个数为1然后递增递减依次交错进行就行啦,同样把没有用的数在第一个序列上坡(即1-n这一段)输出来,就一定能满足题目要求啦。代码实现如下

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
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int ma[N],mn[N],state[N];
int main(){
int t;
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin>>t;
while (t--) {
int n;
cin>>n;
memset(ma, 0, sizeof ma);
memset(mn, 0, sizeof mn);
memset(state, 0, sizeof state);
int a,b;
cin>>a>>b;
if (n%2) {
if((a-b>1)||(a+b>n-2||a>n/2||b>n/2)||(b-a)>1) {cout<<-1<<endl;continue;}
}
else {
if ((a+b>n-2||a>n/2-1||b>n/2-1)||a-b>1||(b-a)>1) {
cout<<-1<<endl;
continue;
}
}
if (a>b) {
int temp=n;
state[1]=1;
state[2]=1;
for (int i=0; i<a; i++) {
ma[i]=temp;
state[temp--]=1;
}
int temp2=3;
for (int i=0; i<b; i++) {

mn[i]=temp2;
state[temp2++]=1;
}
cout<<1<<' ';
for (int i=3; i<=n; i++) {
if (!state[i]) {
cout<<i<<' ';
}
}
for (int i=0; i<a; i++) {
cout<<ma[i]<<' ';
if (i<b) {
cout<<mn[i]<<' ';
}
}
cout<<2<<endl;
}
else if (a<b) {
state[n]=1;
int k=n-1;
state[k]=1;
int temp2=1;
for (int i=0; i<b; i++) {

mn[i]=temp2;
state[temp2++]=1;
}
int temp=n-2;
for (int i=0; i<a; i++) {

ma[i]=temp;
state[temp--]=1;
}
cout<<n<<' ';
for (int i=n-1; i>=1; i--) {
if (!state[i]) {
cout<<i<<' ';
}
}
for (int i=0; i<b; i++) {
cout<<mn[i]<<' ';
if (i<a) {
cout<<ma[i]<<' ';
}
}
cout<<n-1<<endl;
}
else if (a==b) {
state[1]=1;
state[n]=1;
int temp=n-1;
int temp2=2;
for (int i=0; i<a; i++) {
ma[i]=temp;
state[temp--]=1;
mn[i]=temp2;
state[temp2++]=1;
}
cout<<n<<' ';
for (int i=n-1; i>=1; i--) {
if (!state[i]) {
cout<<i<<' ';
}
}
for (int i=0; i<a; i++) {
cout<<mn[i]<<' ';
cout<<ma[i]<<' ';
}
cout<<1<<endl;
}

}
return 0;
}

题目来源:https://www.luogu.com.cn/problem/P1251

这道题选择了一个贪心的策略,即一开始就买下一个适当数量的毛巾,以便以后去洗来用,对于慢洗和快洗的选择是,慢洗越早越好,快洗是在必须得快洗才能满足需求的时候才去选择快洗,并且越晚洗越好,让开销最小化。need数组存储当天用多少毛巾,wash数组存储当天能送洗多少。随着最初买的毛巾数量的变化,最小花销一定是位于极小值点上,所以可以看为一个开口向上的函数,用三分策略降低寻找最优方案的时间复杂度。

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
#include <bits/stdc++.h>
using namespace std;
const int N=1e5+10;
typedef long long LL;
const LL inf=0xfffffffffffff;
int need[N],wash[N],n;
LL sum;
int p,t1,p1,t2,p2;
LL llmin(LL x,LL y)
{
return x<y?x:y;
}
LL minprice(LL firbuy)
{
memset(wash, 0, sizeof(wash));
sum=p*firbuy;
LL rest=firbuy;
for (int i=1; i<=n; i++) {
int today=need[i];
if (rest) {
int k=(int)llmin(rest,today);
today-=k;
rest-=k;
wash[i]+=k;
if (!today) {
continue;
}
}
for (int j=1; j<=i-t1; j++) {
if (wash[j]) {
int k=(int)llmin(wash[j],today);
today-=k;
wash[j]-=k;
wash[i]+=k;
sum+=p1*k;
if (!today) {
break;;
}
}
}
for (int j=i-t2; j>=1&&j>i-t1; j--) {
if (wash[j]) {
int k=(int)llmin(wash[j],today);
today-=k;
wash[j]-=k;
wash[i]+=k;
sum+=p2*k;
}
if (!today) {
break;;
}
}
if (today) {
return inf;
}
}
return sum;
}
int main()
{
cin>>n;
LL l,r=0;
for (int i=1; i<=n; i++) {
cin>>need[i];
r+=need[i];
}
l=need[1];
cin>>p>>t2>>p2>>t1>>p1;
while (l+2<r) {
LL k=(r-l)/3,m1=l+k,m2=r-k;
if (minprice(m1)<minprice(m2)) {
r=m2;
}
else l=m1;
}
LL best=minprice(l);
for (LL i=l; i<=r; i++) {
best=llmin(best,minprice(i));
}
cout<<best;
}

题目来源:BZOJ3969

上道题用到三分的策略,这道题可以直接用二分的策略,因为所有可能的取值是一个线性的函数。所以写一个检查函数,以检查输入的值是否符合条件,不断缩小搜素范围,找到最小值。检查之前先把电池的电量进行排序。检查函数的实现只需要判断可以找到n组电量之差小于所要检查的值,且比所选电池电量大的电池的数量应足够放下其他的位置(芯片中其他电量非最小的位置)

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
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+10;
int n,k,sum,a[N];
bool check(int x)
{
int cnt=n;
for (int i=1; i<sum; i++) {
if (a[i+1]-a[i]<=x) {
if (sum-i+1<2*cnt*k) {
return false;
}
cnt--;
i++;
if (cnt==0) {
return true;
}
}
}
return false;
}
int main()
{
cin>>n>>k;
sum=2*n*k;
for (int i=1; i<=sum; i++) {
cin>>a[i];
}
sort(a+1,a+1+sum);
int l=a[2]-a[1],r=a[sum-k+1],mid=0;

while (l<r) {
mid=l+r>>1;
if (check(mid)) {
r=mid;
}
else l=mid+1;
}
cout<<l;
}

题目来源: https://www.acwing.com/problem/content/description/847/

这是一道宽搜的模版题,把当前数字的排列方式转换成字符串,表示当前的一个状态,同时用哈希表,string查找int的方式存储到达每个状态需要走几步,我们可以让空格和上下左右四个位置交换(在有效的前提下),直到出现符合要求的最终状态。这一位置交换操作用坐标来实现,即定义两个数组,存储(0,1)(0,-1)(1,0)(-1,0)这四种位置移动信息。因为过程是广度优先的(把要查询的状态入队,把该状态的下一步所有的可能状态都遍历一遍,并把它们也入队),所以最终的方案一定是最短的。代码如下

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
#include<iostream>
#include <algorithm>
#include <unordered_map>
#include <queue>
#include <string>
using namespace std;
queue<string> q;
unordered_map<string, int> d;

int bfs(string start)
{
string end="12345678x";
d[start]=0;
q.push(start);
int mx[4]={0,0,1,-1},my[4]={1,-1,0,0};
while (q.size()) {
string t=q.front();
q.pop();
if (t==end) {
return d[t];
}
int distance=d[t];
int k=(int)t.find('x');
int tx=k/3,ty=k%3;
for (int i=0; i<4; i++) {
int x=tx+mx[i],y=ty+my[i];
if (x>=0&&x<3&&y>=0&&y<3) {
swap(t[k],t[3*x+y]);
if (!d[t]) {
q.push(t);
d[t]=distance+1;
}
swap(t[k],t[3*x+y]);
}
}
}
return -1;
}
int main()
{
string start;
char c;
for (int i=0; i<9; i++) {
cin>>c;
start+=c;
}
cout<<bfs(start);
}