文章目录
零、容易忘的语法
printf输出格式(double用lf,int用d)
printf("%.2lf\n",ans);
位运算lowbit
int lowbit(int x){//返回最右侧的1的位置的值
return x&(-x);//-是补码
}
一、搜索
我最喜欢的搜索当然要放第一个!(不是,是因为碎发那题写的我emo了)
14299拼接碎发
AC:
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 30;
int tot = 0;
int n;
int a[N];
int ans;
bool vis[N] = {0};
bool dfs(int l,int now,int used,int ind){
if(used==n && (now==0)) return true;
bool f = false;
int tmpind;
for(int k=ind;k>=1;k--){
if((a[k]+now)>l) continue;
if(vis[k]) continue;//这里如果不写,就会把已经用掉的头发也考虑到去重里面,导致永远找不到答案!
else{
vis[k] = true;
if(a[k]+now==l) tmpind = n;
else tmpind = k-1;
if(dfs(l,(a[k]+now)%l,used+1,tmpind)) {
f = true;
vis[k] = false;
break;
}
vis[k] = false;
}
while(a[k-1] == a[k]) k--;
}
if(f) return true;
else return false;
}
int main(){
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
tot += a[i];
}
for(int i=1;i<=n-1;i++)
for(int j=1;j<=n-i;j++)
if(a[j]>a[j+1]) swap(a[j],a[j+1]);
// sort(a+1,a+n+1);
for(int i=a[n];i<=tot;i++){
if(tot%i) continue;
if(dfs(i,0,0,n)) {
ans = tot/i;
break;
}
}
cout<<ans<<" "<<tot/ans<<endl;
return 0;
}
超时了很多次,不是因为bubble sort(27并不大),是因为剪枝没有剪掉。
第零次剪枝:排序,从最大的发长开始搜
第一次剪枝:数量和长度除得尽
第二次剪枝:搜到大过长度 l 的就停
第三次剪枝:每次搜的一个阶段中去重(因为只选择一根头发)
第四次剪枝:倒着搜,从最长的开始搜,这样如果拼完还不够,只需从比这根头发序号更小的里面搜索(?按理说,正着搜,每次如果不够就从它开始直接往后搜应该也可以的,来不及试了)
二、模拟
11509
#include<iostream>
using namespace std;
int a,b,c,y1,y2,day=0;
int span,span1;
bool runn,flag;
bool run(int yr){
if(yr%4) return false;
if(yr%400==0) return true;
if(yr%100==0) return false;
return true;
}
//1850年1月1日是星期二
int add(int yue,bool runnian){
if((yue==1)||(yue==3)||(yue==5)||(yue==7)||(yue==8)||(yue==10)||(yue==12))
return 3;
if((yue==2) && runnian) return 1;
if((yue==2) && (runnian==0)) return 0;
else return 2;
}
bool check(int yr){
span1 = span;
for(int j=1;j<a;j++){
span1 += add(j,runn);
}
span1 %= 7;
// cout<<"span "<<span1<<endl;
day = (b-1)*7;
for(int j=0;j<=6;j++){
day++;
if((span1+j)%7 == c) break;
}
if((a==1)||(a==3)||(a==5)||(a==7)||(a==8)||(a==10)||(a==12))
if(day>31) return false;
if((a==4)||(a==6)||(a==9)||(a==11))
if(day>30) return false;
//这里这里这里忘写了我的妈
if((a==2) && runn)
if(day>29) return false;
if((a==2) && (runn==0) )
if(day>28) return false;
return true;
}
int main(){
cin>>a>>b>>c>>y1>>y2;
if(c==7) c=0;
span = 2;
for(int k=1850;k<y1;k++){
if(run(k)) span += 2;
else span += 1;
}
span %= 7;//1234560 0是周日
for(int i=y1;i<=y2;i++){
runn = run(i);
if(!check(i)) cout<<"none"<<endl;
else printf("%d/%02d/%02d\n",i,a,day);
if(run(i)) span += 2;
else span += 1;
span %= 7;
}
return 0;
}
忘记判30天的那一段了!!!!!!啊啊啊!!
14255
#include<iostream>
using namespace std;
int n,ans = 0;
int x,y;
int minx(int j,int k){
if(j>k) return k;
else return j;
}
int main(){
cin>>n;
cin>>x;
ans += x;
for(int i=2;i<=n-1;i++){
cin>>y;
ans += minx(x,y);
x = y;
}
ans += x;//这个x和y十分裹人,要考虑n=2的情况
cout<<ans<<endl;
return 0;
}
三、二分
11570
#include<iostream>
using namespace std;
const int N = 100005;
int a[N];
int n,t;
int x;
int find(int l,int r,int k){
while(l<=r){
int mid = (l+r)>>1;
if(a[mid] <= k) l = mid+1;
else r = mid-1;
}
return r;
}
int main(){
cin>>n>>t;
for(int i=1;i<=n;i++){
cin>>a[i];
}
while(t){
cin>>x;
cout<<n-find(1,n,x)<<endl;
t--;
}
return 0;
}
其实我不太敢写二分,因为不知道什么时候+1,-1,而且return也不知道return点什么,有碰运气的感觉,还是需要仔细分析一下.
例如本题,是在排好序的数组中找到比给定数字大的数字个数,面向代码分析 :给个例子
a[1] | a[2] | a[3] | a[4] | a[5] |
---|---|---|---|---|
1 | 3 | 5 | 7 | 9 |
现在查3,4,9
3L | 3R | 3mid | 4L | 4R | 4mid | 9L | 9R | 9mid |
---|---|---|---|---|---|---|---|---|
1 | 5 | 3 | 1 | 5 | 3 | 1 | 5 | 3 |
1 | 2 | 1 | 1 | 2 | 1 | 4 | 5 | 4 |
2 | 2 | 2 | 2 | 2 | 2 | 5 | 5 | 5 |
3 | 2 | 2 | 3 | 2 | 2 | 6 | 5 | 5 |
变成两个元素以后,mid=L,如果左边一个不符合就变到右边一个,L=R=mid,如果右边一个也不符合就L=R+1,L>R,这时候返回的下标是R(R不符合,而R+1是符合的,因为右边界是R是由R+1符合而导致的);如果左边一个符合就R=mid-1=L-1,现在变成R<L,返回R=L-1,为什么L-1不符合呢,因为左边边界比L-1大是由L-1不符合导致的,所以现在R=L-1不符合而L符合!
11994
泪目,我好像可以写二分算法了
#include<iostream>
using namespace std;
int n,m,x,t;
int a[100005];
int b[100005] = {0};
int erfen(int k){
int l = 1,r = n;
while(l<=r){
int mid = (l+r)>>1;
if(b[mid]>=k) r = mid-1;
else l = mid+1;
}
return r;
}
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>a[i];
b[i] = b[i-1]+a[i];
// cout<<b[i]<<endl;
}
for(int i=1;i<=m;i++){
cin>>x;
t = erfen(x);
cout<<t+1<<" "<<x-b[t]<<endl;
}
return 0;
}
1414
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 200005;
int n,m,k;
int l,r,mid;
int a[N] = {0},aa[N] = {0};
bool check(int t){
int cnt = 0,ans = 0;
for(int j=1;j<=n;j++){
if(a[j]>t) cnt = 0;
if(a[j]<=t) {
cnt++;
if(cnt>=k){
ans++;
cnt -= k;
}
}
}
if(ans>=m) return true;
else return false;
}
int main(){
cin>>n>>m>>k;
for(int i=1;i<=n;i++){
cin>>a[i];
aa[i] = a[i];
}
sort(aa+1,aa+n+1);
l = 1;
r = n;
while(l<=r){
mid = (l+r)/2;
if(check(aa[mid])){
r = mid-1;
}
else {
l = mid+1;
}
}
if(n<k*m) cout<<"-1"<<endl;
else cout<<aa[l]<<endl;
return 0;
}
做法是二分时间。写的时候出事的是check函数,里面<=和>写反了……
而且第一次写还把-1的判断条件写错了……
四、快速幂算法
14258
#include<iostream>
using namespace std;
long long pow2[37];
int a,cnt = 0;
long long n,ans = 1;
int main(){
cin>>a>>n;
pow2[0] = a;//意思是a的2^0次方,是a
//a的2^n次方 = (a的2^n-1次方)*(a的2^n-1次方)
for(int i=1;i<=35;i++){
pow2[i] = (pow2[i-1]*pow2[i-1])%2019;
// cout<<pow2[i]<<endl;
}
while(n){
if(n%2) ans *= (pow2[cnt]*(n%2))%2019;
//attention!如果不加if,可能输出来都是0!!
//因为是乘法,只能乘上可以分解出来的
ans %= 2019;
// cout<<ans<<endl;
n /= 2;
cnt++;
}
cout<<ans<<endl;
return 0;
}
五、滑动窗口
1401 经典无重复字符串滑窗O(n)
#include<iostream>
#include<cstring>
using namespace std;
const int N = 10000005;
char ch[N];
int num[30];
int ans = 0,len,head = 0;
int main(){
cin>>ch;
len = strlen(ch);
for(int i=0;i<len;i++){
num[ch[i]-'a']++;
if(num[ch[i]-'a']>=2){
while(num[ch[i]-'a']>=2){
num[ch[head]-'a']--;
head++;
}
}
ans = max(ans,i-head+1);
}
cout<<ans<<endl;
return 0;
}
滑动窗口复习:无重复字母的字符子串
see see this
1405 三数和->两数和滑窗
O(n3)……
O(n2logn)第三层用二分查找
O(n2)滑动窗口
AC代码:
#include<iostream>
#include<algorithm>
using namespace std;
int a[10005] = {0};
int n,X,m;
bool f = false;
int main(){
cin>>n>>X;
for(int i=1;i<=n;i++){
cin>>a[i];
}
sort(a+1,a+1+n);
for(int i=1;i<=n;i++){
if(a[i]>=X) break;
m = X-a[i];
int x = 1,y = n;
while(x<y){
if(a[x]+a[y]==m && x!=i && y!=i) break;
while(a[x]+a[y]<m && x<y) x++;
while(a[x]+a[y]>m && x<y) y--;
if(x==i) x++;
if(y==i) y--;
}
if(f==0 && x<y) {
f = true;
if(a[i]<=a[x]) cout<<a[i]<<" ";
cout<<a[x]<<" ";
if(a[i]>a[x] && a[i]<=a[y]) cout<<a[i]<<" ";
cout<<a[y]<<" ";
if(a[i]>a[y]) cout<<a[i];
cout<<endl;
}
}
if(!f) cout<<"-1"<<endl;
return 0;
}
题目说明:只要求输出第一组解
解题思路:第一层枚举一个n,第2和3用滑窗,一个从左开始一个从右开始,和大了右指针左移,和小了左指针右移,确实可以找到答案。
1418 包含全部字母的最短字串
#include<iostream>
#include<cstring>
#include<climits>
using namespace std;
const int N = 2000005;
char s[N],t[N];
int lens,lent,head = 0,tail = -1,tcnt = 0,cnt = 0;
int ans = INT_MAX;
int num[55] = {0};
int stad[55] = {0};
int ss[N];
int main(){
cin>>s>>t;
lens = strlen(s);
lent = strlen(t);
for(int i=0;i<lent;i++){
if(t[i]>='a' && t[i]<='z') {
stad[t[i]-'a']++;
if(stad[t[i]-'a']==1) tcnt++;
}
else {
stad[t[i]-'A'+26]++;
if(stad[t[i]-'A'+26]==1) tcnt++;
}
}
for(int i=0;i<lens;i++){
if(s[i]>='a' && s[i]<='z') ss[i] = s[i]-'a';
else ss[i] = s[i]-'A'+26;
}
while(head<lens){
while(cnt<tcnt && tail<lens-1){
tail++;
num[ss[tail]]++;
if(num[ss[tail]]==stad[ss[tail]]) cnt++;
}
while(stad[ss[head]]==0 && head<lens-1) head++;
if(cnt == tcnt) {
ans = min(ans,tail-head+1);
}
if(stad[ss[head]] == num[ss[head]])
cnt--;
num[ss[head]]--;
head++;
}
if(ans == INT_MAX) cout<<"-1"<<endl;
else cout<<ans<<endl;
return 0;
}
笑死,这个题目里面说“重复次数相同”,意思是要求出现的字母出现次数必须只能多不能少!然后还有 data里面的数据是错的(。)无语了真
这个滑窗是相当于 tail一直往后走走到符合,然后head不断往后,每一个head算一次ans
1413 正好包含不同k个数字的子序列
注意本题的特点在于:数字可以重复,导致思路变化:以head为主,tail为副,head每到一个有ans的位置时让tail去滑,滑到不能再滑了让tail回到原位,再head++,做到不重不漏。
AC代码:
#include<iostream>
#include<cstring>
using namespace std;
int a[200005];
int apr[200005] = {0};
int n,k,head,tail,cnt = 0,ans = 0;
int main(){
cin>>n>>k;
for(int i=1;i<=n;i++)
cin>>a[i];
head = tail = 1;
cnt++;
apr[a[1]]++;
while(head<=tail && tail<=n){
while(cnt<k && tail<n) {
tail++;
if(apr[a[tail]]==0) cnt++;
apr[a[tail]]++;
}
while(cnt>k){
if(apr[a[head]]==1) cnt--;
apr[a[head]]--;
head++;
}
if(cnt==k) {
ans++;
}
int tail1 = tail;
while(cnt==k && apr[a[tail+1]]>0 && tail<n){
tail++;
apr[a[tail]]++;
ans++;
}
while(tail>tail1){
apr[a[tail]]--;
tail--;
}
if(apr[a[head]]==1) cnt--;
apr[a[head]]--;
head++;
}
cout<<ans<<endl;
return 0;
}
六、贪心
1389
#include<iostream>
using namespace std;
long long n,m,ans = 0,tmp = 0;
int mas[505],x[1005];
int lastapr[505];
bool fir = true;
bool flag[505];
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>mas[i];
lastapr[i] = 0;
}
for(int i=1;i<=m;i++){
cin>>x[i];
if(!lastapr[x[i]]){
// else {
// tmp += mas[x[i]];
// ans += tmp;
// cout<<x[i]<<" "<<ans<<endl;
// }
if(fir)
fir = false;
else ans+=tmp;
tmp += mas[x[i]];
}
else{
for(int j=lastapr[x[i]]+1;j<i;j++){
flag[x[j]] = false;
}
for(int j=lastapr[x[i]]+1;j<i;j++){
if(!flag[x[j]]) {
ans += mas[x[j]];
flag[x[j]] = true;
}
}
}
lastapr[x[i]] = i;
}
cout<<ans<<endl;
return 0;
}
and、其他小算法
摩尔投票1404
排序,相同的数放在一起数个数
O(n)摩尔投票算法
算阶乘的非零末位1385
我的80分代码:(最后20分T了)
#include<iostream>
#include<cstring>
using namespace std;
int cnt5 = 0;
int n,ans = 1;
int main(){
cin>>n;
int tmp = 5;
while(n>tmp){
cnt5 += n/tmp;
tmp *= 5;
}
for(int i=2;i<=n;i++){
int k=i;
while(cnt5 && k%2==0) {
cnt5--;
k /= 2;
}
while(k%5==0) k /= 5;
ans = (ans%10)*(k%10);//其实只需要保留最后一位就够了!!!
}
// cout<<ans<<endl;
cout<<ans%10<<endl;
return 0;
}
具体的AC算法可见EXAM3题解(在作业文档/大学计算机课/机考),里面有一点数学猜想与证明可以到O(logn)(我没认真看)
未知1、读题困难
1381(同时还有位运算)
这是一个令人尴尬的题目……因为我做了好多遍才做过(
首先的问题是题意理解,一开始以为必须是留m个杯子,每个杯子都是2k升的果汁,结果发现可以比m个杯子少(这题不是贪心),然后还是写不过,又发现每个杯子里面果汁量可以不一样多,只要都是2x就可以了。
然后就是效率问题,先看一下正确理解题意以后的第一个tle代码
#include<iostream>
using namespace std;
long long n,m,x;
int get2(int x){
int y=0;
while(x){
y += x%2;
x /= 2;
}
return y;
}
int main(){
cin>>n>>m;
x = n;
while(get2(x)>m){
x++;
}
cout<<x-n<<endl;
return 0;
}
简单是简单,思路也很简单,每次给杯子数加1,然后等二进制分解出来的1少于等于m的时候停止,T了两个点
第二遍的代码:
#include<iostream>
#include<climits>
using namespace std;
long long n,m,now,tmp;
int cnt = 0;
int main(){
cin>>n>>m;
now = n;
tmp = n;
while(tmp){
cnt += tmp%2;
tmp /= 2;
}
while(cnt>m){
tmp = now;
while(tmp%2){
cnt--;
tmp/=2;
}
cnt++;
now++;
}
cout<<now-n<<endl;
return 0;
}
把算二进制分解后的1的操作简化了,变成了模拟进位的算法,T了一个点,都是3000多ms了
最后AC的代码:
#include<iostream>
using namespace std;
int m,n,cnt = 0,posi = 0;
long long pow2[32],ans = 0;
int one[32];
int main(){
cin>>n>>m;
pow2[0] = 1;
for(int i=1;i<=31;i++){
pow2[i] = 2*pow2[i-1];
}
while(n){
if(n%2) {
cnt++;
one[cnt] = posi;
}
posi++;;
n /= 2;
}
if(cnt<=m) cout<<"0"<<endl;
else {
int num = cnt-m+1;
for(int i=1;i<=num;i++){
ans += pow2[one[i]];
}
ans = pow2[one[num]+1]-ans;
cout<<ans<<endl;
}
return 0;
}
优化的想法是把1的位置存起来,只需要取用1的位置就行了,计算的时候只需要O(log2n)的复杂度
其实也可以用位运算得到二进制为1的数位(请复习)
顺便 今天1024程序员节
语法点:位运算lowbit
int lowbit(int x){//返回最右侧的1的位置的值
return x&(-x);//-是补码
}
未知2、你的脑子不好使
1384 前缀和,但是我下标写的很混乱
AC代码:
#include<iostream>
#include<cstring>
using namespace std;
char ch[10000005];
int upp[10000005] = {0};
int ans = 10000005;
int main(){
cin>>ch;
int len = strlen(ch);
for(int i=0;i<len;i++){//到i为止有多少大写,1base
if(ch[i]>='A'&&ch[i]<='Z') upp[i+1] = upp[i]+1;
else upp[i+1] = upp[i];
}
for(int i=0;i<=len+1;i++){//记得前面留0后面len+1,不然容易少掉结果:修改后全都是小写或全都是大写
if(ans>i-2*upp[i]+upp[len]) //算出来ans是修改后到i(包括i)全为大写的结果
ans = i-2*upp[i]+upp[len];
}
cout<<ans<<endl;
return 0;
}
未知3、几个点被坑
1、数据类型:比如说没开long long或者忘记用double——以后先考虑这个数据范围爆掉的问题,不能再错!
14277 没开long long
10^9*1000绝对爆int,以后做题先把可能的数字算一下
1359 送分题,爆double的要写高精度!
#include<iostream>
#include<string>
using namespace std;
string s1,s2;
int f1,f2;
long long a=0,b=0,c=0,d=0,ans1=0,ans2=0;
int main(){
cin>>s1>>s2;
for(int i=1;i<=s1.length() ;i++){
f1 = i-1;
if(s1[i-1]=='.') break;
a = a*10+s1[i-1]-'0';
// cout<<s1[i-1]-'0'<<endl;
}
// cout<<a<<endl;
for(int i=1;i<=s2.length() ;i++){
f2 = i-1;
if(s2[i-1]=='.') break;
b = b*10+s2[i-1]-'0';
}
for(int i=f1+2;i<=s1.length() ;i++){
c = c*10+s1[i-1]-'0';
}
for(int i=f2+2;i<=s2.length() ;i++){
d = d*10+s2[i-1]-'0';
}
ans1 = a*b;
ans1 = ans1+(a*d)/1000000000;
ans1 = ans1+(c*b)/1000000000;
ans2 = (a*d)%1000000000+(c*b)%1000000000+(c*d+500000000)/1000000000;
ans1 += ans2/1000000000;
ans2 %= 1000000000;
cout<<ans1<<'.';
printf("%09lld",ans2);
return 0;
}
更多资料可见之前的博客
54254 关于int是怎么爆掉的
WA代码
#include<iostream>
using namespace std;
int a,b,c;
long long del1,del2;
int main(){
cin>>a>>b>>c;
del1 = b*b;
del2 = 4*a*c;
if (del1 < del2)
cout<<"0"<<endl;
if (del1 == del2)
cout<<"1"<<endl;
if (del1 > del2)
cout<<"2"<<endl;
return 0;
}
AC代码
#include<iostream>
using namespace std;
long long a,b,c;
long long del1,del2;
int main(){
cin>>a>>b>>c;
del1 = b*b-4*a*c;
if (del1 < 0)
cout<<"0"<<endl;
if (del1 == 0)
cout<<"1"<<endl;
if (del1 > 0)
cout<<"2"<<endl;
return 0;
}
这是怎么一回事呢,是因为int在运算过程中会爆掉!如果右边int*int或者int+int可能会爆int,那么结果可能就会出错,应该是因为计算时是从右边往左边走的吧(srds减法可能不会,因为某些二进制存储及进位的原因会抵消影响但是你不要抱着这种心理去尝试好不好)
2、题目设的坑,容易惯性思维掉进去
1388 给出区间端点不一定是顺序的
未知4、一点点trick
1402(1026机考)
60分代码:
#include<iostream>
using namespace std;
const int N = 50005;
long long a,b,c;
int ans = 0,n;
int x[N];
long long x1[N],x2[N];
int main(){
cin>>n;
for(int i=1;i<=n;i++){
cin>>x[i];
}
while(1){
cin>>a>>b>>c;
a += ans;
b += ans;
c += ans;
if(a==0&&b==0&&c==0) break;
if(a==0&&b==0) {
ans = -c;
cout<<ans<<endl;
continue;
}
for(int i=1;i<=n;i++){
if(a*(i+1)*x[i]*x[i]+(b+1)*i*x[i]+c+i==0){
ans = i;
break;
}
}
cout<<ans<<endl;
}
return 0;
}
笑死,改进的时候把i*x[i]*x[i]
和i*x[i]
全部存到了另外两个数组,结果全TLE了
这题的做法是:倒推,得到ans[n-1]然后四元方程组不停跑