- A New Year and Counting Cards
题意:每张牌两面分别是字母和数字。定义一个规则:如果一张牌的字母是元音字母那么另一面一定对照着偶数。给你n个牌的一面,问你最少翻多少张牌最终判断这些牌的对照是正确的。
解题思路:直接统计有多少个是元音字母或者是奇数。
根据以上规则,1.元音字母只能对应偶数。2.偶数可以对应所有字母,而奇数只能对应非元音字母。
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 <iostream>
#include<ctype.h>//isdigit(),isalpha()函数的头文件
using namespace std;
bool isyuanyin(char s){
if(s=='a'||s=='e'||s=='i'||s=='o'||s=='u')return true;
else return false;
}
int main(){
string s;
int cnt;
int tmp1;
char tmp2;
while(getline(cin,s)){
cnt=0;
for(int i=0;i<s.length();i++){
if(isdigit(s[i])){//判断是否为数字
tmp1=int(s[i]);
if(tmp1&1==1)cnt++; //判断是否为奇数
}
else if(isalpha(s[i])){//判断是否为字母
tmp2=s[i];
if(isyuanyin(tmp2)){
cnt++;
}
}
}
cout<<cnt<<endl;
//要清空 s吗,不需要每次都清空s,在getline的时候就会把s清空的
}
}
解法二(统计元音和奇数个数):
#include<bits/stdc++.h>
using namespace std;
string c="aeiou13579";
int main()
{
string s;
cin>>s;
int ans=0;
for(int i=0;s[i];i++)
for(int j=0;c[j];j++)
if(s[i]==c[j])
ans++;
cout<<ans;
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
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
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
| #include <iostream>
using namespace std;
int a[55][55];
// 1上 2 下 3 左 4 右
int s1,s2,e1,e2;
int ans;
int n,m;
string abc;
int main()
{
mem(a,0);
cin>>n>>m;
string str;
ans=0;
for(int i=0;i<n;i++)
{
cin>>str;
for(int j=0;j<str.length();j++)
{
if(str[j]=='.')
a[i][j]=1;
else if(str[j]=='#')
a[i][j]=2;
else if(str[j]=='S')
{
s1=i,s2=j;
//a[i][j]=1;
}
else if(str[j]=='E')
{
//a[i][j]=1;
e1=i;e2=j;
}
}
}
//cout<<s1<<" "<<s2<<" "<<e1<<" "<<e2<<endl;
cin>>abc;
for(int x=1;x<=4;x++)
{
for(int y=1;y<=4;y++)
{
for(int z=1;z<=4;z++)
{
for(int d=1;d<=4;d++)
{
if(x!=y&&x!=z&&x!=d&&y!=z&&y!=d&&z!=d)
{
// cout<<x<<y<<z<<d<<endl;
int i,j,c;
for(i=s1,j=s2,c=0;c<abc.length();c++)
{
if(abc[c]=='0')
{
if(x==1)//shang
i-=1;
else if(x==2)//xia
i+=1;
else if(x==3)//zuo
j-=1;
else if(x==4)//you
j+=1;
}
else if(abc[c]=='1')
{
if(y==1)
i-=1;
else if(y==2)
i+=1;
else if(y==3)
j-=1;
else if(y==4)
j+=1;
}
else if(abc[c]=='2')
{
if(z==1)
i-=1;
else if(z==2)
i+=1;
else if(z==3)
j-=1;
else if(z==4)
j+=1;
}
else if(abc[c]=='3')
{
if(d==1)
i-=1;
else if(d==2)
i+=1;
else if(d==3)
j-=1;
else if(d==4)
j+=1;
}
if(a[i][j]==2)
break;
if(i<0||i>=n||j<0||j>=m)
break;
if(i==e1&&j==e2)
{
// cout<<x<<" "<<y<<" "<<z<<" "<<d<<endl;
ans++;
break;
}
}
}
}
}
}
}
cout<<ans<<endl;
return 0;
}
/*
解法二:
#include<bits/stdc++.h>
using namespace std;
string s[51];
int a[6],sx,sy;
int main()
{
//4!=24种 最大24种 24种排列,就是a4 4
//a[0],a[1],a[2],a[3]分别带别上下左右,来全排列找到结果
int n,m;
cin>>n>>m;
for(int i=0;i<n;i++)
cin>>s[i];
for(int i=0;i<n;i++)
for(int j=0;j<m;j++)
if(s[i][j]=='S')sx=i,sy=j;
string c;
cin>>c;
for(int i=0;i<4;i++)
a[i]=i+48;//把a[i]转换为与字符型相对应的a[i]
int ans=0;
do
{
int f=0;
int tx=sx,ty=sy;
for(int i=0;c[i];i++)
{
if(c[i]==a[0])
tx-=1;
else if(c[i]==a[1])
tx+=1;
else if(c[i]==a[2])
ty-=1;
else ty+=1;
if(tx<0||ty<0||tx>=n||ty>=m)
break;
if(s[tx][ty]=='#')break;
if(s[tx][ty]=='E'){f=1;break;}
}
ans+=f;
}while(next_permutation(a,a+4));
cout<<ans;
return 0;
}*/
|
题意:给你编号1~n的花园,花园从左到右排成一排。其中有k个花园有水龙头,当水龙头所在的花园浇满后,会流向左右相邻花园,现在同时打开所有水龙头,问你多长时间后n个花园能被浇满。
解法一:思路:这个题目很有意思,只用把握一点就好:所有水龙头同时开启,所有水流同时流动。所以先看边界情况,从编号最小的水龙头到最左端,这一段只有一个方向的水流,最右端也是同理,这两个边界都只有一个方向的水流,而且它们同时流动,所以边界所花时间是两者中较大的:ans=max(a[1]-1,n-a[k])。然后是中间部分,一定是来自两个方向的水流,此时要看间隔的个数x,如果x是奇数,那么时间就是x/2+1,偶数就是x/2,这个自己想一下就能想明白,而且所有中间间隔的双向水流都是同时流动,所以依然是取最大值即可,而不是累加。
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
| 解法一:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll a[110],n,k;
int main()
{
ll t;
scanf("%lld",&t);
while(t--)
{
scanf("%lld%lld",&n,&k);
for(ll i=1;i<=k;i++)scanf("%lld",&a[i]);
ll ans=max(a[1]-1,n-a[k]); //处理边界的单向水流
for(ll i=2;i<=k;i++)
{
ll tmp=a[i]-a[i-1]-1;
if(tmp&1)tmp=tmp/2+1;
else tmp/=2;
ans=max(ans,tmp); //更新中间的双向水流
}
printf("%lld\n",ans+1);
}
return 0;
}
解法二:
#include <bits/stdc++.h>
using namespace std;
bool check(bool flag[], int n) {
for (int i = 1; i <= n; i++) {
if (flag[i] == false) return true;
}
return false;
}
int main() {
int cas;
cin >> cas;
for (int i = 1; i <= cas; i++) {
int n, k;
cin >> n >> k;
int val[k];
for (int j = 0; j < k; j++) {
cin >> val[j];
}
//1-n
bool flag[n+1];//对应1-n
for (int j = 1; j <= n; j++) flag[j] = false;
for (int j = 0; j < k; j++) {
flag[val[j]] = true;//标记已填满
}
int cnt = 1;
while (check(flag, n)) {
//对每次的进行遍历,每次都遍历val数组中的每个元素
int change[n];
int count = 0;
for (int j = 1; j <= n; j++) {
if (flag[j] == true) {
int left = j-1;//
int right = j+1;
if (left >= 1 && flag[left] == false) {
// flag[left] = true;
change[count++] = left;
}
if (right <= n && flag[right] == false) {
// flag[right] = true;
change[count++] = right;
}
}
}
for (int j = 0; j < count; j++) flag[change[j]] = true;
cnt++;
}
cout << cnt << endl;
}
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
| #include<bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 10;
bool vis[maxn];
int l[maxn];
int r[maxn];
int res[maxn];
int main(){
int T;cin>>T;
while(T--) {
memset(vis,0,sizeof vis);
int n;cin>>n;
for(int i = 0;i < n;i++) cin>>l[i]>>r[i];
for(int i = 1;i <= 5100;i++) {
bool flg = false;
for(int j = 0;j < n;j++) {
if(!vis[j]) {
flg = true;break;
}
}
if(!flg) break;
for(int j = 0;j < n;j++) {
if(vis[j]) continue;
if(i > r[j]) {
vis[j] = true;
res[j] = 0;
}
if(l[j] <= i && r[j] >= i) {
vis[j] = true;
res[j] = i;
break;
}
}
}
for(int i = 0;i < n;i++) {
printf("%d ",res[i]);
}cout<<endl;
}
return 0;
}
|
- E-A Compatible Pair
就是两个数组,其中前一个数组中去掉一个最大之后,与后一个数组相乘,求这个相乘的最大值。
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
130
131
132
133
134
135
| #include<iostream>
#include<cstdio>
#include<queue>
#include<cstring>
#include<algorithm>
#define IO ios::sync_with_stdio(false);cin.tie(0);
#define INF 1e18
typedef long long ll;
using namespace std;
ll n, m, a[110], b[110];
ll maxm;
int main()
{
while(cin >> n >> m){
for(int i = 0; i < n; i++){
cin >> a[i];
}
for(int i = 0; i < m; i++){
cin >> b[i];
}
sort(a,a+n);
sort(b,b+m);
maxm=max(a[n-2]*b[m-1],a[1]*b[0]);
//为什么最大值不是排序完了之后这两个的值呐,因为最大值有可能如下面那种解法
cout << maxm << endl;
}
return 0;
}
/*解法一:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll INF_LL = 9223372036854775807LL;
ll a[maxn];
ll b[maxn];
int main(){
ios_base::sync_with_stdio(0);
int n,m;cin>>n>>m;
for(int i = 0;i < n;i++) cin>>a[i];
for(int i = 0;i < m;i++) cin>>b[i];
sort(a,a+n);
sort(b,b+m);
ll a1 = a[0],a2 = a[1],a3 = a[n - 2],a4 = a[n - 1];
ll b1 = b[0],b2 = b[1],b3 = b[m - 2],b4 = b[m - 1];
ll res = -INF_LL;
if(n > 2) {
res = max(res,a2*b1);
res = max(res,a2*b4);
res = max(res,a3*b1);
res = max(res,a3*b4);
res = max(res,min(a[n - 1]*b[m - 1],a[0]*b[0]));
//就有可能少了这种情况,所以只比较那两种结果会漏掉
res = max(res,min(a[n - 1]*b[0],a[0]*b[m - 1]));
} else {
res = max(res,a2*b1);
res = max(res,a3*b4);
}
cout<<res<<endl;
return 0;
}
*/
/*解法二:
#include<iostream>
#include<cstdio>
#include<queue>
#include<cstring>
#include<algorithm>
#define IO ios::sync_with_stdio(false);cin.tie(0);
#define INF 1e18
typedef long long ll;
using namespace std;
ll n, m, a[110], b[110];
int main()
{
while(cin >> n >> m){
for(int i = 0; i < n; i++){
cin >> a[i];
}
for(int i = 0; i < m; i++){
cin >> b[i];
}
ll maxm = -INF, loc;
for(int i = 0; i < n; i++){
for(int j = 0; j < m; j++){
if(a[i]*b[j] > maxm){
maxm = a[i]*b[j];
loc=i;//记录最大值的下标,然后再一次遍历求最大值
}
}
}
maxm=-INF;//这样写代表maxn为负无穷小,这样就不会在比较时把更小的数给漏掉了
for(int i = 0; i < n; i++){
for(int j = 0; j < m; j++){
if(i == loc)
continue;
if(a[i]*b[j] >maxm){
maxm = a[i]*b[j];
}
}
}
cout << maxm << endl;
}
return 0;
}
*/
/*解法三:
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
int main(){
int n,m;LL a[55],b[55],c[55];
cin>>n>>m;
for(int i=0;i<n;++i)cin>>a[i];
for(int i=0;i<m;++i)cin>>b[i];
for(int i=0;i<n;++i){
c[i]=a[i]*b[0];
for(int j=1;j<m;++j)
c[i]=max(c[i],a[i]*b[j]);
//其实这个把那个c[i]的值存放 b[0]到b[m-1],b这一整个数组的最大值
// ????那有可能第二大的值是从a[i]*b[j] 里面运算的时候出现了呐
//,不会的因为a[i]取得的最大值要把此时的a[i]去掉,所以才会有 最大值,第二大,,,,第n*m-n大的值只能从c[]中取得
}
sort(c,c+n);cout<<c[n-2]<<endl;
return 0;
}
*/
|
- F-A Prosperous Lot
题意: 0 4 6 9有一个环,8有两个环,其他没有环。求出一个数字,上面数位的环和答案为给定的x。如果这个数字超过1e18,输出-1.
思路: 很明显,贪8就可以了。到了37,就超过1e18了。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| #include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
int main()
{
int n;scanf("%d",&n);
if(n > 36)printf("-1\n");
else if(n % 2 == 0)
{
for(int i = 1;i <= n / 2;i++)
printf("%d",8);
}
else
{
printf("4");
for(int i = 1;i <= n / 2;i++)
printf("%d",8);
}
return 0;
}
|
- G Subsequence
给定长度n的数列整数,以及整数s,求出总和不小于S的连续子序列的长度的最小值。
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
| 解法一(不利用尺取法,利用lower_bound():
#include <iostream>
#include <algorithm>
#include <cstring>
#include<cstdio>
using namespace std;
int n,ss;
int a[100005],s[100005];
int main(){
int t;
cin>>t;
while(t--){
memset(a,0,sizeof(a));
memset(s,0,sizeof(s));
int kk=100005;
cin>>n>>ss;
for(int i=1;i<=n;i++){
cin>>a[i];
s[i]=s[i-1]+a[i];
}
if(s[n]<ss)
cout<<"0"<<endl;
else{
for(int i=1;s[i]+ss<=s[n];i++){
int k=lower_bound(s+i,s+n+1,s[i]+ss)-s;
kk=min(kk,k-i);
}
cout<<kk<<endl;
}
}
return 0;
}
其中lower_bound该函数的用法:
在从小到大的排序数组中,
lower_bound( begin,end,num):从数组的begin位置到end-1位置二分查找第一个大于或等于num的数字,找到返回该数字的地址,不存在则返回end。通过返回的地址减去起始地址begin,得到找到数字在数组中的下标。
upper_bound( begin,end,num):从数组的begin位置到end-1位置二分查找第一个大于num的数字,找到返回该数字的地址,不存在则返回end。通过返回的地址减去起始地址begin,得到找到数字在数组中的下标。
在从大到小的排序数组中,重载lower_bound()和upper_bound()
lower_bound( begin,end,num,greater<type>() ):从数组的begin位置到end-1位置二分查找第一个小于或等于num的数字,找到返回该数字的地址,不存在则返回end。通过返回的地址减去起始地址begin,得到找到数字在数组中的下标。
upper_bound( begin,end,num,greater<type>() ):从数组的begin位置到end-1位置二分查找第一个小于num的数字,找到返回该数字的地址,不存在则返回end。通过返回的地址减去起始地址begin,得到找到数字在数组中的下标。
|
解法二:
因为枚举满足条件的区间,要枚举起点和终点,所以时间复杂度是o(n^3),所以考虑只枚举起点或者终点,即要去掉最后一个循环,所以:我们试试只枚举终点。对于终点j,我们的目标是要找到一个让Bj-Bi-1≥S,且i尽量大(i越大,序列长度j-i+1就越小)的i值,也就是找一个让Bi-1≤Bj-S最大的i。考虑图1-29所示的序列。(前缀和技巧,数组B就是存储前几项的和)
当j=5时,B5=12,因此目标是找一个Bi-1≤12-7=5的最大i。注意到B是递增的(别忘了,本题中所有Ai均为整数),所以可以用二分查找。
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
|
#include <iostream>
#include <stdio.h>
#include <algorithm>
#include <string.h>
using namespace std;
#define N 100001
int A[N];
int B[N];
//二分查找最接近target但不大于target
int BinarySerach(int target,int R){
int L = 0;
int mid = 0;
while(L < R){
mid = L + (R - L) / 2;
if(B[mid] > target){
R = mid;
}
else{
L = mid + 1;
}
}
return L;
}
int main(){
int n,s,i,j;
while(scanf("%d %d",&n,&s) != EOF){
int minLen = n+1;
B[0] = 0;
for(i = 1;i <= n;i++){
scanf("%d",&A[i]);
//序列前缀和
B[i] = B[i-1] + A[i];
}
for(j = 1;j <= n;j++){
int target = B[j] - s;
//二分查找
int index = BinarySerach(target,j-1);
if(index > 0){
minLen = min(minLen,j-index+1);
}
}
//没有满足条件的序列
if(minLen == n+1){
minLen = 0;
}
cout<<minLen<<endl;
}
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
61
62
63
64
65
66
67
| 解法三(尺取法求前多少项的和,就是l和r两个指针,找到一个符合条件的区间,l++,子区间和减左边的第一个元素,然后在向后比较,这样把整个区间都走一遍,就能得到最重的结果。):
#include<iostream>
using namespace std;
#define min(a,b) a>b?b:a;
#define MAXN 100010
#define INF 0xfffffff
int a[MAXN];
int main()
{
int n,N,S,l,r,len;
long long sum = 0;
cin >>n;
while(n--){
cin>>N>>S;
for(int i=0;i<N;i++){
cin>>a[i];
}
l = r = 0;//初始化
sum = 0;
len = INF;
while(1){
while(r<N && sum<S){
sum+=a[r];
r++;
}//此时的sum是区间[l,r)上的和
if(sum<S){//如果说 sum<S但是r>=N了 结束循环
break;
}
len = min(len,r-l);//更新长度 取较小的
sum -= a[l];//看看减去左边一个元素 还满不满足sum>=S
l++; //l右移一个
}
if(len == INF){//如果说len没有变化 说明所有的数加起来<S
cout<<"0"<<endl;
}
else{
cout<<len<<endl;
}
}
return 0;
}
变式:最大子列和问题
给定N个整数的序列 a1,a2,,,,,, an 求 求做大子列和,如果子列和为负数,那么结果为0
#include<iostream>
using namespace std;
int a[100100];
int main()
{
int maxsum,sum,N;
maxsum = sum = 0;
cin>>N;
for(int i=0;i<N;i++){
cin>>a[i];
}//先读入数据
for(int i=0;i<N;i++){
sum += a[i];
if(sum > maxsum){
maxsum = sum;
}
else if(sum <0 ){
sum = 0;
}
}
cout<<maxsum<<endl;
return 0;
}
|
可以看到高度h = y1 + sqrt(2r*2r - (x2-x1)(x2-x1));枚举个最高的高度即可,因为 一旦碰到就不会落下了
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
| #include<bits/stdc++.h>
using namespace std;
int h[1500];
double res[1500];
int abs(int x,int y) {if(x - y > 0) return x - y;return y - x;}
int main(){
ios_base::sync_with_stdio(0);
int n;
double r;
cin>>n>>r;
for(int i = 0;i < n;i++) {
cin>>h[i];
double rs = r;
for(int j = 0;j < i;j++) {
int dx = abs(h[i],h[j]);
if(dx <= 2*r) {
rs = max(rs,res[j] + sqrt(4*r*r - dx*dx));
}
}
res[i] = rs;
}
for(int i = 0;i < n;i++) printf("%.10f ",res[i]);
return 0;
}
|
- H-Jessica’s Reading Problem
题目大意:
XXX要准备考试,书总共有P页,第i页恰好有一个知识点ai,书中的同一个知识点可能会被多次提到,所以他希望看其中连续的一些页的书来把所有的知识点都给看完。。
题目分析:
其实页数可以看作连续的序列,然后就是要求出一个子序列,子序列的要求的包涵所有知识点都有的页数,且子序列的长度要最短。
首先我们并不知道总共有多少个知识点,这是个先要记录的。
不断的把序列中的元素加入到子序列中,知识点的数目会不断的增加,当知识点是数目满了之后,把子序列前面的元素开始剔除,再看看现在的子序列还是否满足这个条件。满足继续删除子序列前面的元素,否则的话把后面的元素加入到子序列中,然后按上述步骤继续执行。
注意中间过程知识点数的增加或减少的维护。
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
| 尺取法求前多少项出现的次数(与前面那种求前多少项的和区别开来):
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <queue>
#include <map>
#include <set>
#include <vector>
using namespace std;
typedef long long LL;
const int N=100000+999;
int n,m;
int num[N];
//尺取法
int main()
{
while(scanf("%d",&n)!=EOF)
{
memset(num,0,sizeof(num));
set<int> all; //利用集合元素的单一性
map<int,int> cot;
for(int i=0; i<n; i++)
{
scanf("%d",&num[i]);
all.insert(num[i]);
}
int res=n;
m=all.size(); //算出种类
int s=0,e=0,sum=0;
while(1)
{
while(e<n && sum<m)
{
if(cot[ num[e++] ]++ == 0) //如果这个种类未出现 则sum++
sum++;
}
if(sum<m) break;//这一句是为了让最后e=n之后,能跳出外层的while循环。
res=min(res,e-s);
if(--cot[ num[s++] ] == 0) //如果这个种类-1后为0 则sum-1
sum--;
}
printf("%d\n",res);
}
return 0;
}
|
- I Graveyard Design
求一组连续整数的平方和的问题,可以用尺取法模拟一个符合要去的区间的左边和右边,找到符合要求的区间就记录下来。
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
| #include<cstdio>
#include<cstring>
#include<cmath>
#define LL __int64
/*
一般来说,64位整型的定义方式有long long和__int64两种(VC还支持_int64),
而输出到标准输出
方式有printf(“%lld”,a),printf(“%I64d”,a),和cout << a三种方式。
*/
int num[2000],left[2000],right[2000];
//对于数组,若下标大于1e7,就应该放到数组外面声明才可以
void solve(LL n)
{
int i,j;
LL m=(LL)sqrt(n*1.0);//因为sqrt只能对double类型运算
LL sum=0,l=1,r=0;
int cnt=0;
while(1)
{
while(sum<n)
{
r++;
sum+=r*r;
}
if(r>m)
break;
//这一步是为了让当r>m的时候,已经不会有连续整数的平方和大于m了,
//所以要跳出外层的while循环
if(sum==n)
{
num[cnt]=r-l+1;
left[cnt]=l;
right[cnt]=r;
cnt++;
}
sum-=(l*l);
l++;
}
printf("%d\n",cnt);
for(i=0;i<cnt;++i)
{
printf("%d ",num[i]);
for(j=left[i];j<right[i];++j)
printf("%d ",j);
printf("%d\n",right[i]);
}
}
int main()
{
LL n;
while(scanf("%I64d",&n)!=EOF)
solve(n);
return 0;
}
|
讲义:
1.区间求和:
前缀和,o(1)时间内求出前n项的和,或者某个区间的和(两个区间头减尾)
前缀和
前缀和S[i]=A[1]+A[2]+……+A[i];
可以用A[i]=S[i]-S[i-1]来还原;
还有二维的前缀和S[i][j] = S[i-1][j] + S[i][j-1] - S[i-1][j-1] + A[i][j];
这个公式可以用容斥原理推得。
2.区间修改:
单点查询(差分法)
差分就是将数列中的每一项分别与前一项数做差,例如:
一个序列 1 2 5, 4 7 3, 差分后得到1 1 3, -1 3 -4, -3
上面整个序列+2后: 3 4 7, 6 9 5 3 1 3, -1 3 -4, -5
// 只有整个区间的首和尾不同,其他的差分相同 ,即将首+2,(尾+1)的位置-2
这里注意得到的差分序列第一个数和原来的第一个数一样(相当于第一个数减0)
差分序列最后比原序列多一个数(相当于0减最后一个数)
性质:
1、差分序列求前缀和可得原序列
2、将原序列区间[L,R]中的元素全部+x,可以转化操作为差分序列L处+x,R+1处-x
3、按照性质2得到,每次修改原序列一个区间+x,那么每次差分序列修改相应的位置 增加的和减少的相同
tips:前缀和与差分是一对逆运算。差分操作有助于把原序列上的“区间操作”
转化为差分序列上的“单点操作”,在树上有着独特的应用。
-
尺取法:
头尾双指针
1
2
3
4
5
6
7
8
9
| 形式为:
while(1){
while(表达式1){
if(条件1)语句1
}
if(条件2){break;}
语句2
if(条件3)语句3
}
|
其中:表达式1为边界判断条件,目的是让在语句1中移动左指针,直到移动到符合要求的那个地方;条件2是边界判断(如:个数超过要求的总个数,sum超过要求的总和,其实就是左指针从头跑到尾了,可以结束外层while了;语句2可以执行一些操作,如对结果的处理,如res=min(res,e-s);条件3为边界判断,是左指针右移后对结果的处理,
如:
if(–cot[ num[s++] ] == 0) //如果这个种类-1后为0 则sum-1
sum–; //这样的话,就是集合中少了那一种元素,对结果产生了影响, 就应该对结果进行相应的处理;
其中语句2和语句3试情况而定,可调换顺序或者没有该语句;
- 4.要想区间求和并且实现单点查询 分为根下n块,每一块就有根下n个数
5 4 1, 8 10 1, 8 7 6, 5 4 => 求区间第三个到第十个的和
10 19 21 9 =>那么结果就是1+19+21+5
-
- 5 4 6, 8 10 1, 8 7 6,10 4
重新分块后: 5 4 6, 8 1 2, 3 4 10, 1 8 7, 6 10 4
=>若果在第四个位置插入4个数,那么可以用再分块(当插入的数大于
根下12为3,所以先3个一组,)的方法来维系块的性质
-
[省选题](https://www.luogu.org/problem/P4799)
100 1500, 500 500 1000
=>分为两块进行搜索,在第一个块中进行搜索,在第二个快中进行搜索,再拼凑起来,对结果是没有影响的
折半搜索,分治
参考blog:https://www.cnblogs.com/ZAGER/p/9827160.html
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
| 其中:1-4组数据N≤20,爆搜就可以解决。
inline void dfs(R ll dep,R ll sum){
if(sum>m)return;//只要sum大于总的m就可以结束本次递归
if(dep==n+1){//只要满足搜索到的深度到了n+1了,就可以退回上一步了
ans++;//ans初始化为0
return;
}
dfs(dep+1,sum+a[dep]);// 买这一次的票
dfs(dep+1,sum);//不买这一次票
}
int main(){
read(n);read(m);
for(R int i=1;i<=n;i++)read(a[i]);
if(n<=20){
dfs(1,0); //从深度为1,到n+1, 一共有n次
printf("%lld\n",ans);
}
return 0;
}
5-7组数据M≤106,裸的背包啊。
int main(){
read(n);read(m);
for(R int i=1;i<=n;i++)read(a[i]);
if(m<=1e6){
f[0]=1;
for(R int i=1;i<=n;i++)
for(R int j=m;j>=a[i];j--)
f[j]+=f[j-a[i]]; //这一句到底是什么意思
for(R int i=0;i<=m;i++)ans+=f[i];
printf("%lld\n",ans);
}
return 0;
}
```
![upload successful](\aoyue\images\pasted-188.png)
![upload successful](\aoyue\images\pasted-189.png)
|
#include
#include
#include
#include
#include
#define ll long long
#define R register
#define N 55
using namespace std;
templateinline void read(T &a){
char c=getchar();T x=0,f=1;
while(!isdigit(c)){if(c==’-‘)f=-1;c=getchar();}
while(isdigit(c)){x=(x<<1)+(x<<3)+c-‘0’;c=getchar();}
a=f*x;
}
ll n,m,w[N],mid,suma[1<<21],sumb[1<<21],cnta,cntb,ans;
inline void dfs(R int l,R int r,R ll sum,R ll a[],R ll &cnt){
if(sum>m)return;
if(l>r){
a[++cnt]=sum;
return;
}
dfs(l+1,r,sum+w[l],a,cnt);
dfs(l+1,r,sum,a,cnt);
}
int main(){
read(n);read(m);
for(R int i=1;i<=n;i++)read(w[i]);
mid=n>>1;
dfs(1,mid,0,suma,cnta);
dfs(mid+1,n,0,sumb,cntb);
sort(suma+1,suma+1+cnta);
for(R int i=1;i<=cntb;i++)
ans+=upper_bound(suma+1,suma+1+cnta,m-sumb[i])-suma-1;
printf(“%lld\n”,ans);
return 0;
}
tips:
在从小到大的排序数组中:
lower_bound( begin,end,num):从数组的begin位置到end-1位置二分查找第一个大于或等于num的数字,找到返回该数字的地址,不存在则返回end。通过返回的地址减去起始地址begin,得到找到数字在数组中的下标。
upper_bound( begin,end,num):从数组的begin位置到end-1位置二分查找第一个大于num的数字,找到返回该数字的地址,不存在则返回end。通过返回的地址减去起始地址begin,得到找到数字在数组中的下标。
`
暴力可以加上点技巧(技巧性的暴力):如分块,前缀和,差分