仲舟の奇妙算法(一)【个人版】

1.求01图中全是1的最大子矩阵
问题:n*m的01图,输出全为1的最大子矩阵中1的个数 (n,m<103)
算法类型:动态规划
时间复杂度:o(nm)
空间复杂度:o(nm)
算法思路:将输入的矩阵a[][]转化为当前位置列以上的连续1个数h[][],再通过left[]和right[]来记录以此位置的h[][]为矩阵高,左右最长距离(所以左右的数必须大于等于h[][]才能构成矩阵),然后通过行1数乘列1数即(right[]-left[]-1)*h[][]来算包含当前位置的全1最大矩阵。
算法难点:计算每一行的left[]和right[],通过h[]从左往右比较,for(; now && h[i][j] <= h[i][now]; now=left[now]);用于找到比h[][]相等和更大的最左值, now=left[now]用并查集的方法快速访问全相等时最左端的值,right[]同理。
研究时间:2021.7.20 3h

#include <stdio.h>
#include <algorithm>
#include <string.h>
#include <stack>
using namespace std;
const int N = 500 + 5;

int n,m;
int a[N][N];
int h[N][N];
int left[N],right[N];

int main() {
    while(scanf("%d%d",&n,&m) == 2) {
        memset(h,0,sizeof h);
        for(int i=1; i<=n; i++) for(int j=1; j<=m; j++) scanf("%d",&a[i][j]);
        for(int i=1; i<=n; i++) {
            for(int j=1; j<=m; j++) {
                if(a[i][j] == 1) h[i][j] = h[i-1][j] + 1;
                else h[i][j] = 0;
            }
        }
       /* for(int i=1; i<=n; i++)
            for(int j=1; j<=m; j++)
                {
                    printf("%d",h[i][j]);
                    putchar(j==m?'\n':' ');
                }*/
        int ans = 0;
        for(int i=1; i<=n; i++) {
            for(int j=1; j<=m; j++) {
                int now = j - 1;
                for(; now && h[i][j] <= h[i][now]; now=left[now]);//!!!
                left[j] = now;
            }
            for(int j=m; j>=1; j--) {
                int now = j + 1;
                for(; now != m+1 && h[i][j] <= h[i][now]; now=right[now]);//!!!
                right[j] = now;
            }
            for(int j=1; j<=m; j++)
            {
                ans = max(ans, (right[j]-left[j]-1)*h[i][j]);
                //printf("right[%d]=%d,left[%d]=%d,(%d-%d-1)*h[%d][%d]=%d\n",j,right[j],j,left[j],right[j],left[j],i,j, (right[j]-left[j]-1)*h[i][j]);
            }
        }
        printf("%d\n",ans);
    }
    return 0;
}

input:
在这里插入图片描述

debug:
h[][]
在这里插入图片描述

left[],right[],(right[]-left[]-1)*h[][]
在这里插入图片描述

output:
在这里插入图片描述

2.多次操作单次输出问题
原题:https://ac.nowcoder.com/acm/contest/11253/K
问题:规则是有个队列,每次放入一个数i,比i大的数都要被消除,输入数字总数n,有k个要求,每个要求是放入第p个数中队列还有x个数,求1~n的放入顺序,如果无法实现就输出-1。(n,k<106)
算法类型:new
时间复杂度:o(3n+k)
空间复杂度:o(4n)
算法思路:∵(n,k<10^6),所以不能根据要求一遍遍操作,而且要按照要求顺序操作,每个要求操作以后部分范围值必须出结果,否则必T,通过要求可知,若放入第p个数中队列还有1个数,这个数将比前面的都小,如果剩1数中最大的p,那么这个位置一定是1,换句话说,应该从后向前顺序放置数字,如果剩x个就在前面随机x-1个位置放x-1个顺序数,存入第i个数前有b[i]个数,m[i]为p-x即已删除的个数作为判断-1的条件,若删除的个数突然减少就-1,用一个栈来存还没放进去的数字,并且始终保持栈是顺序出来,a[]用来存结果。
算法难点:从后往前找有要求的a[],若存在x个数,再从a[]到a[-x+1]存在的数依次放回栈中,栈依然顺序,再从a[-x+1]到a[]依次放入,此时a[-x+1]到a[]顺序,符合题目要求。
研究时间 2021.7.19 3h

#include<bits/stdc++.h>
#define N 1000010
using namespace std;
int n,k,p,x,a[N],b[N],m[N],s=0;
stack<int >q;
int main(){
	cin>>n>>k;
	memset(b,0,sizeof b);
	memset(a,0,sizeof a);
	memset(m,0,sizeof m);
	for(int i=n;i>=1;i--)
	q.push(i);
	while(k--){
		cin>>p>>x;
		if(x>p){
			cout<<-1<<endl;
			return 0;
		}
		b[p]=x;
		m[p]=p-x;
		if(m[p]>s)
		s=m[p];
	}
	/*cout<<"i  :";
	for(int i=1;i<=n;i++)
        cout<<i<<" ";
    cout<<endl<<"b[]:";
    for(int i=1;i<=n;i++)
        cout<<b[i]<<" ";
    cout<<endl<<"m[]:";
    for(int i=1;i<=n;i++)
        cout<<m[i]<<" ";
    cout<<endl;*/
	for(int i=n;i>=1;i--){
		if(b[i]!=0){
			for(int j=i;j>=i-b[i]+1;j--){
				if(a[j]!=0)
				q.push(a[j]);
			}
			for(int j=i-b[i]+1;j<=i;j++){
			a[j]=q.top();
			q.pop();
			}
			if(m[i]>s){
				cout<<-1<<endl;
				return 0;
			}
			else s=m[i];
		}
		/*printf("i=%d,a[]:\n",i);
		for(int i=1;i<=n;i++)
           cout<<a[i]<<" ";
       cout<<endl;*/
	}
	for(int i=1;i<=n;i++){
		if(a[i]==0){
			a[i]=q.top();
			q.pop();
		}
		i==1?cout<<a[i]:cout<<" "<<a[i];
	}
}

input:
在这里插入图片描述
debug:
在这里插入图片描述
output:
在这里插入图片描述

3.多维多种情况次数计算问题
原题:https://ac.nowcoder.com/acm/contest/11213/D
问题:有a,b,c三堆钞票,每次只能拿走一堆的300/450/750元,直到不能拿为止,有几种拿法(一开始就不能拿也算一种)(结果对1000000007求余)(a,b,c<104)
算法类型:动态规划
时间复杂度:o(<n2)
空间复杂度:o((n/50)3)
算法思路:这是一个多情况问题,对于每种情况,之后的情况又有多种情况,这种就需要用到递归的dp了,但由于是三维数组,104*3必爆栈,还好给的数据可以除以50以减小内存,开一个2003的dp[][][],记录当钞票为a b c时的结果,那么这个结果等于3*3结果之和即dp[][][]=dp[-300/50][][]+dp[][-300/50][]+dp[][][-300/50]+dp[-450/50][][]+……
算法难点:dp题目如果询问t次的就要所有dp,如果只询问一次就选择dp。dp算法好写,就是理解起来困难点,要注意递归中如果dp有数据就直接用数据,没有就要经过计算,还要注意如果买不起的时候要置1而不是0,因为dp是下一级dp之和,最底层的基至少得有1。
研究时间:2021.7.21 2h

#include <bits/stdc++.h>
using namespace std;
const int p = 1000000007;
int a, b, c;
int dp[203][203][203];
int DP(int x, int y, int z) {
  if (dp[x][y][z] != -1) return dp[x][y][z];
  int ret = 0;
  if (x >= 6) (ret += DP(x - 6, y, z)) %= p;
  if (x >= 9) (ret += DP(x - 9, y, z)) %= p;
  if (x >= 15) (ret += DP(x - 15, y, z)) %= p;
  if (y >= 6) (ret += DP(x, y - 6, z)) %= p;
  if (y >= 9) (ret += DP(x, y - 9, z)) %= p;
  if (y >= 15) (ret += DP(x, y - 15, z)) %= p;
  if (z >= 6) (ret += DP(x, y, z - 6)) %= p;
  if (z >= 9) (ret += DP(x, y, z - 9)) %= p;
  if (z >= 15) (ret += DP(x, y, z - 15)) %= p;
  if (ret == 0) ret = 1;
  //printf("dp[%d][%d][%d]=%d\n",x,y,z,ret);
    return dp[x][y][z] = ret;
}
int main() {
  cin >> a >> b >> c;
  a /= 50;
  b /= 50;
  c /= 50;
  memset(dp, -1, sizeof dp);
  //printf("a/50=%d b/50=%d c/50=%d\n",a,b,c);
  cout << DP(a, b, c) << endl;
  return 0;
}

input:
在这里插入图片描述
debug:
在这里插入图片描述
output:
在这里插入图片描述

4.博弈问题
原题:https://ac.nowcoder.com/acm/contest/11166/A
问题:有两堆石头,ab两人拿,每人可以从一堆拿x个,在另一堆拿kx(k>=0)个,谁刚好拿完谁胜,a先拿,双方最右策略,输入两堆数n,m(n,m<5*103),输出获胜者
算法类型:筛法
时间复杂度:o(>nm)
空间复杂度:o(nm)
算法思路:类似这种最优策略问题,往往是给出初始条件就知道谁赢的“还没开始就已经结束”的游戏,进而可以将题目转化为操作次数奇a赢偶b赢,再深入就是可以从a的第一次拿开始分析——如果a一次性全拿完,则a赢;如果a一次性拿不完,就必须控制剩下偶数次数让自己获胜,如果怎么样都无法控制只能是b赢。第一个好理解,第二个类似数学归纳法(算法就是dp)——假设n=2,m=3确定了b会赢,那么我拿一次剩n=2,m=3时a会赢,拓展一下就是a拿一次后到达b赢的结果,那就是a赢。
算法难点:如何判断所给的n喝m能否操作一次到达以往计算过的b赢时的n’和m’,就成了主要的难题,而且如果从后往前判断,要把前面的全部的b访问一次并计算,就成o(n3)了,必T,那我们不如反向思考,如果生成一个b赢的情况n,m,将之后一步所有情况(n+x,m+kx(x,k在不越界的情况下从1到5000)全部算为a,再往后找b,如果不是a则就是b,这样就节省了不少时间,还大大简便了运算,这个方法就叫——筛法。
研究时间:2021.7.25 3h

#include <bits/stdc++.h>
using namespace std;
const int MAXN = 5005;
bool F[MAXN][MAXN]= {0};

int main() {
    int cnt = 0;
    for(int i=0; i<=5000; i++)
        for(int j=0; j<=5000; j++)
            if (!F[i][j]) {
                for (int k = 1; i + k <= 5000; k++)
                    for (int l = 0; j + k * l <= 5000; l++)
                        F[i + k][j + k * l] = 1;
                for (int k = 1; j + k <= 5000; k++)//由于m与n无顺序,需要反向再算一遍
                    for (int l = 0; i + k * l <= 5000; l++)
                        F[i + k * l][j + k] = 1;
            }
    int T;
    cin >> T;
    while (T--) {
        int n,m;
        cin >> n >> m;
        puts(F[n][m] ? "Alice" : "Bob");
    }
    return 0;
}

input&output:
在这里插入图片描述

5.无向完全连通图求三角形个数问题
原题:https://ac.nowcoder.com/acm/contest/11254/J
问题:有n个结点构成的无向完全连通图,所有边通过生成种子已经确定是白边或者黑边,求这个图中用相同颜色边构成的三角形的总数。(n<=8000)
算法类型:逆向思维
时间复杂度:o(n2)
空间复杂度:o(n2)
算法思路:如果要算同色边三角形的个数,首先可以想到随机列举三个点,再判断是否能构成同色,那这样就是n3,必t,如果n<=8000的话,只能每两个点(即一条边)全部遍历,达到n2或者n2logn的算法,那如果只能算两个点的话,是不能判断能构成三角形的,这题如果去计算多少个三角形,怎么想都是n3,那能不能逆向思维,判断有多少个一定构不成三角形的,将总数减去构不成的三角形就是所求的三角形呢?答案是肯定的。以一个点为顶点,如果他的两条边是同色,它可能构成三角形,如果是异色,它必构不成同色三角形,那我们可以通过计算一个点的所有白线和黑线总数a,b,那这个点产生的异色对有ab个,答案就是n(n-1)(n-2)-sum(aibi)/2。(因为每条边会重复算,所以要除2)
算法难点:就是难想,写倒是挺好写
研究时间:2021.7.25 3h

#include<bits/stdc++.h>
using namespace std;
 
namespace GenHelper//生成种子
{
    unsigned z1,z2,z3,z4,b,u;
    unsigned get()
    {
        b=((z1<<6)^z1)>>13;
        z1=((z1&4294967294U)<<18)^b;
        b=((z2<<2)^z2)>>27;
        z2=((z2&4294967288U)<<2)^b;
        b=((z3<<13)^z3)>>21;
        z3=((z3&4294967280U)<<7)^b;
        b=((z4<<3)^z4)>>12;
        z4=((z4&4294967168U)<<13)^b;
        return (z1^z2^z3^z4);
    }
    bool read() {
      while (!u) u = get();
      bool res = u & 1;
      u >>= 1; return res;
    }
    void srand(int x)
    {
        z1=x;
        z2=(~x)^0x233333333U;
        z3=x^0x1234598766U;
        z4=(~x)+51;
        u = 0;
    }
}
using namespace GenHelper;
int s0[8005],s1[8005];
int n,seed;
int main(){
    scanf("%d%d",&n,&seed);
    srand(seed);
    for (int i=1;i<=n;i++)
        for (int j=i+1;j<=n;j++)
            if (read()) ++s1[i],++s1[j];
            else ++s0[i],++s0[j];
    long long ans=0;
    for (int i=1;i<=n;i++)
        ans+=1ll*s1[i]*s0[i];
    ans=1ll*n*(n-1)*(n-2)/6-(ans/2);
    cout<<ans<<endl;
}

input&output

在这里插入图片描述

6.双条件统排问题
原题:https://codeforces.com/contest/1551/problem/C
问题:t用例,每个用例有n个字符串,每个字符串只有abcde五种字母,找出用最多条字符串拼成的长字符串,使得长字符串出现最多字母的次数大于其他字母出现的总次数。(t<5000,n<2*105)
算法类型:统排+贪心
时间复杂度:o(tn)
空间复杂度:o(tn)
算法思路:如果只记录个数不记录字符串顺序,那就先把问题转化为统排问题,随后可以通过以某个字母排序来从最多开始加起,再判断是否满足条件,满足就更新答案,存在这种问题:如下图用例5,对d排序后是d,d,cbdca,a,e,在加到第三个时,不满足条件,之后都不满足,输出为2,但如果我选d,d,a,又满足条件,答案是3。像这样,我们又不能把所有组合的情况数出来(否则必t),那到底哪出错了呢?如果是这种数据,必须是只能o(n)才能保证不超时,求最大值用贪心也没有错,错就错在统排上:我们希望加x字母多而非x字母少的字符串,而排序不可能按两个要求同时排序,如果要,就得给出一个新的比较条件,将两个条件结合成一个新的条件再比较,这成了突破口。x多且非x少,可以转化为x占比率,通过占比率排序,从多往少的加一定没错。
算法难点:我们在统排的时候,如果记录x个数时,可以让其+1,其余的-1,如果所有的都种方法,可以简化成全部数先减strlen(),然后出现一个字母+2,这样cbdca的d值为-3,a的d值却成了-1,a的d占比率高,排在了前面,用贪心再算值,就不会出错了。
(本题含有重载符号,对其不了解的同学可以去了解一下)
研究时间:2021.7.27 3h

#include<bits/stdc++.h>
#define N 200005
using namespace std;

int T,n,m,Ts;char c[N];
struct node{
	int a[5];
	bool operator<(const node&b)const{//重载(返回类型 operator 重载符号(const 结构体类型 & 变量名)
		return a[Ts]>b.a[Ts];
	}
}s[N];
int main(){
	cin>>T;
	while(T--){
		cin>>n;
		for(int i=1;i<=n;++i){
			scanf("%s",c+1);m=strlen(c+1);
			for(int j=0;j<5;++j)s[i].a[j]=-m;
			for(int j=1;j<=m;++j)s[i].a[c[j]-'a']+=2;
		}
		int ans=0;
		for(Ts=0;Ts<5;++Ts){
			int sum=0;
			sort(s+1,s+n+1);
			for(int i=1;i<=n;++i){
				sum+=s[i].a[Ts];
				if(sum<=0)break;
				ans=max(ans,i);
			}
		}
		cout<<ans<<"\n";
	}
	return 0;
}

input&output:
在这里插入图片描述

7.多次查询与维护问题
原题:https://ac.nowcoder.com/acm/contest/11255/I
问题:有一个1~n不重复数组a[],他们每个数可以不变或者+1,输出操作完后的最小逆序数对总数。(n<2*105)
算法类型:①二分+归并排序 ②线段树/树状数组
时间复杂度:o(nlogn)
空间复杂度:o(3n)
算法思路:通过多次举例可以发现,如果一个数x+1,最多只能消除前面比它大1的逆序数,如果前面没有这个数,+1没有任何作用,如果后面还有比它小1的数y,x+1之之后还会使y的前面多个逆序数,所以就不加,就能求出修改后的a[],最后就是求a[]的逆序数对了。
算法难点:至于怎么求逆序数对,有两种方法:①归并排序求逆序数对:该方法是先修改后求逆序数。输入时同时记录数值和下标,对数值进行快排后顺序操作,随后按下标快排用归并排序计算逆序对,原理是每个归并如[a,b,c][d,e,f]如果a比d大,由于归并两子序列都是顺序排列,所以b和c也比a大,d的逆序数就是3,用ans += mid - i + 1;就可计算出。②树状数组求逆序数对:该方法是先求原逆序数后修改。也是输入时同时记录数值和下标,假设有一个全为0的数组b[],从前往后算原a[]每个的逆序数,算x之前的逆序数就是b[1]到b[n]的和减b[1]到b[x]的和,ans+=sum(n)-sum(x),再将b[x]=1,将其全转为树状数组的形式(即下面的t[])存储就完工了。最后通过下标判断x+1是否在x前面,如果是就ans–,并且立flag=1,使x-1不再变化。
(注:特别注意ans范围,如果n=2x105,ansmax=n(n-1)/2=1010,得开longlong!!!(我的就是被longlongwa了qwq))
研究时间:2021.7.27 2h

①归并排序求逆序数对(自己AC的)

#include<bits/stdc++.h>
#define js ios::sync_with_stdio(false);cin.tie(0); cout.tie(0)
#define ll long long
using namespace std;
const ll MAXN= 2e6+700;
struct node {
    int data;
    int pos;
} a[MAXN],b[MAXN];
ll n,ans=0;

bool cmp(node a,node b) {
    return a.data<b.data;
}

bool dmp(node a,node b) {
    return a.pos<b.pos;
}

void merge(int low, int mid, int high) {
    int i, j, k;
    for (i = low, j = mid + 1, k = i; i <= mid && j <= high; k++) {
        if (b[i].data <= b[j].data)
            a[k].data = b[i++].data;
        else {
            a[k].data = b[j++].data;
            ans += mid - i + 1;
        }
    }
    while (i <= mid) 
        a[k++].data = b[i++].data;
    while (j <= high) 
        a[k++].data = b[j++].data;
    for (i = low; i <= high; i++) 
        b[i].data = a[i].data;
}

void mergesort(int low, int high) {
    if (low < high) {
        int mid;
        mid = (low + high) / 2;
        mergesort(low, mid);
        mergesort(mid + 1, high);
        merge(low, mid, high);
    }
}

int main() {
    js;
    cin>>n;
    for(int i=1; i<=n; i++) {
        cin>>a[i].data;
        a[i].pos=i;
    }
    sort(a+1,a+1+n,cmp);
    for(int i=1; i<=n-1; i++)
        if(a[i].pos>a[i+1].pos) {
            a[i].data++;
            swap(a[i],a[i+1]);
            i++;
        }
    sort(a+1,a+1+n,dmp);
    for(int i=1;i<=n;i++)
        b[i]=a[i];
    mergesort(1, n);
    cout<<ans<<endl;
    return 0;
}

②树状数组求逆序数对(推荐)

#include<bits/stdc++.h>
#define ll long long
using namespace std;

long long ans;
const int N=200005;
int n,p[N],a[N],t[N];

void change(int k) {
   // printf("  into change(%d)\n",k);
    for (; k<=n; k+=k&(-k)) {
        t[k]++;
       // printf("    t[%d]++,t[%d]=%d\n",k,k,t[k]);
    }
}
int ask(int x) {
    //printf("  into ask(%d)\n",x);
    int s=0;
    for (; x; x-=x&(-x)) {
        s+=t[x];
       // printf("    s+=t[%d],t[%d]=%d,s=%d\n",x,x,t[x],s);
    }
    return s;
}
int main() {
    scanf("%d",&n);
    for (int i=1; i<=n; i++)
        scanf("%d",&a[i]),p[a[i]]=i;
   /* cout<<"a[]"<<" ";
    for(int i=1; i<=n; i++)
        cout<<a[i]<<" ";
    cout<<endl<<"p[]"<<" ";
    for(int i=1; i<=n; i++)
        cout<<p[i]<<" ";
            cout<<endl;*/
    for (int i=1; i<=n; i++) {
        ans+=ask(n)-ask(a[i]);
        //printf("i=%d,ans+=ask(%d)-ask(%d),ans=%d\n",i,n,a[i],ans);
        change(a[i]);
    }
    int fl=0;
    for (int i=2; i<=n; i++)
        if (p[i]>p[i-1]) fl=0;
        else if (fl==1) fl=0;
        else {
            ans--;
            fl=1;
        }
    cout<<ans<<endl;
}

input:
在这里插入图片描述
②debug:
树状数组的查询与维护
在这里插入图片描述
output:
在这里插入图片描述

8.恐怖的高等数学
原题:https://ac.nowcoder.com/acm/contest/11254/E
问题:t个用例,每个用例输入n,求1~n所有满足x2+y2=k(xy+1) (k>=1)的(x,y)个数。(t<105,n<1018)
算法类型:数论
时间复杂度:o(tlogn+³√n)
空间复杂度:o(³√n)
算法思路:先看数据,遇到1018,必须是o(logn)或o(1)(即给个数直接通过公式算出结果)的算法才不会超时,而这题是求前面之和,和前面有关系往往不是用公式算的,很有可能涉及到降n的次再运算。再看题干,是二元二次方程,遇到二次方往往用韦达定理,由于是二元,我们先固定x,y用韦达定理是y+y’=kx,yy’=x2-1,也就是说如果我(x,y)满足该式子,那么(x,y’)也满足,不妨设x<=y,则由上述式子得y’>=0且y‘<=y,当我将y’替换成y时,又多一组解,如此循环,最后发现确定了一个x的值y可以一直递降到0,当y’=0时,y=kx,则x2+k2x2=k(kx2+1),k=x2(k已经确定),此时y=x3则在递降到最后一层时,总有(x,x3)满足条件,之后就可以逆推,令y=x3,由韦达得(kx-y,x)也成立(注:在这kx-y比x小)然后再令x’=kx-y,y’=x,又有(x’,y’)成立,可遍历到所有结果。而且对于x3的乘法,可以将复杂度o(1018)降为o(106)。大大提高运行效率。又因为(a1,a2)(a2,a3)……所有对的第一个都是由前面一个对的第二个,所以有多少对就相当于有多少个,可以将每个答案存入数组,通过upper_bound返回下标值,该下标值就是前面满足条件个数的和,即题目答案。
算法难点:类似上述递归,可以用到类似辗转相除法的算法计算(即a=x,b=y,存答案后c处理a和b,然后让a=b,b=c,完成两处理量同时右移一步的操作。
(注:这里特别注意数据是否会溢出,1LL因为循环的x是int,相乘需要在前面转换成longlong,__int128是怕longlong相乘会爆,无法比较是否越界,所以还得开int128!!!)
研究时间:2021.7.28 4h

#include<bits/stdc++.h>
#define js ios::sync_with_stdio(false);cin.tie(0); cout.tie(0)
#define ll long long
using namespace std;
const ll INF = 1e18+7;
vector<ll>ans{1};

int main() {
    js;
    for (int x = 2; x <= 1000000; x++) {
       // cout<<"x="<<x<<endl;
        ll a = x,b = 1LL*x*x*x;//注意数据大小
        while (1) {
            //cout<<b<<endl;
            ans.push_back(b);
            if (__int128(b) * x * x - a > INF)//注意数据大小
                break;
            ll c = b * x * x - a;
            a = b;
            b = c;
        }
    }
    sort(ans.begin(), ans.end());
   /*for(ll i=0;i<ans.size();i++)
        cout<<ans[i]<<endl;*/
    int t;
    cin >> t;
    while (t--) {
        ll n;
        cin >> n;
        cout <<upper_bound(ans.begin(), ans.end(), n) - ans.begin()<< endl;
    }
    return 0;
}

input&output:
在这里插入图片描述

debug:
当x=2时所生成的结果
在这里插入图片描述

9.多维度模拟问题
原题:https://ac.nowcoder.com/acm/contest/11254/F
问题:有n张卡,卡的数字1~13,要求n张卡通过+ − - ∗ * /运算后等于m并且这n张卡所有等于m的运算结果必须存在过分数(1/1,2/2都不属于分数,2/3才属于),输出所有结果总和以及每一种有效解的卡的组合。(1<=n<=4,m<109)
算法类型:模拟
时间复杂度:o(约132n)
空间复杂度:o(106)
算法思路:既然要求组合,再看这题数据就十位数,这题直接模拟(暴力)没有问题!都暴力了思路也很简单:四个数开四个for,从四个1for到四个13,然后再开两个for,随机选两个进行一个for的四种运算,然后再开一个for和下一个数四种运算,剩下一个再开……思路没有问题,但这么多for怎么写呢?而且随n的不同,开的for也不同,对于这种多维度的问题,最好用递归来写,来康康我们北大同学AC的代码(不愧是北大,我就代码看了三个多小时qwq)……
算法难点:本题难点较多,请仔细阅读代码中的注释!
研究时间:2021.7.28 4h

#include<bits/stdc++.h>
using namespace std;

int n,m,ansn;
vector<double> nw;
vector<int> ans[1000010],nww;

int solve(vector<double> nw)
{
	if (nw.size()==1)//终止递归条件:nw只有一个数
	{
		if (fabs(nw[0]-m)<1e-9) return 2;//因为有除号,算出来的结果可能会有误差,如果组合的结果和答案偏差1e-9的话,就认为这是没有分数运算的无效答案,返回2(error)。
		return 0;//构不成m,但以后可能构成,先待定(undetermined)
	}
	int sz=nw.size(),ans=0;
	for (int i=0; i<sz; i++)//在所有数中抽取第i个数准备处理
		for (int j=i+1; j<sz; j++)//在所有数中抽取第j个数准备处理
		{
			vector<double> Nw; Nw.clear();//新开一个给下一个递归的数组
			for (int k=0; k<sz; k++) if (k!=i&&k!=j) Nw.push_back(nw[k]);//把除i和j剩下的放进去
			Nw.push_back(nw[i]+nw[j]);//再把i和j相加处理
			ans=max(ans,solve(Nw));//处理后放下一个递归
			if (ans==2) return 2;//出现解立即返回2(error),终止递归,排除该答案
			Nw.pop_back();//把刚刚处理的拿走
			Nw.push_back(nw[i]*nw[j]);//再把i和j相乘处理
			ans=max(ans,solve(Nw));//下面同理
			if (ans==2) return 2;
			Nw.pop_back();
			Nw.push_back(nw[i]-nw[j]);
			ans=max(ans,solve(Nw));
			if (ans==2) return 2;
			Nw.pop_back();
			Nw.push_back(nw[j]-nw[i]);//由于减有先后性,需要反减一遍
			ans=max(ans,solve(Nw));
			if (ans==2) return 2;
			Nw.pop_back();
			if (fabs(nw[j])>1e-9)//由于除法可能会存在除0的非法操作,所以这里要判断
			{
				Nw.push_back(nw[i]/nw[j]);
				int nww=2;
				if (nw[i]/nw[j]-floor(nw[i]/nw[j]+1e-8)>1e-9) nww=1;//floor是向下取整函数,ceil是向上取整函数,如果nw[i]除nw[j]的不是分数,那么这次结果是2。
				ans=max(ans,min(nww,solve(Nw)));//如果nww为2,再给之后运算机会,如果还有分数,min(2,1);如果nww为1,已经做了一次除法,之后没分数也不要紧,min(1,2)。
				if (ans==2) return 2;
				Nw.pop_back();
			}
			if (fabs(nw[i])>1e-9)
			{
				Nw.push_back(nw[j]/nw[i]);
				int nww=2;
				if (nw[j]/nw[i]-floor(nw[j]/nw[i]+1e-8)>1e-9) nww=1;
				ans=max(ans,min(nww,solve(Nw)));
				if (ans==2) return 2;
			}
		}
	return ans;
}

int main()
{
	scanf("%d%d",&n,&m);
	for (int i=1; i<=13; i++)//第一个数
		for (int j=i; j<=(n>=2?13:i); j++)//第二个数
			for (int k=j; k<=(n>=3?13:j); k++)//第三个数
				for (int l=k; l<=(n==4?13:k); l++)//第四个数
				{
					nw.clear(),nww.clear();//nww存结果,nw拷贝用来运算
					nw.push_back(i),nww.push_back(i);
					if (n>1) nw.push_back(j),nww.push_back(j);
					if (n>2) nw.push_back(k),nww.push_back(k);
					if (n>3) nw.push_back(l),nww.push_back(l);
					if (solve(nw)==1) ans[++ansn]=nww;//我每种情况只能有一种结果,所以用1来表示用了除法是有效解(true),0用来表示当前运算还没有找到有效解(以后可能会有)(undetermined),2用来表示有方法没有用用除法得到的无效解(error),立即终止继续递归,减少运算量
				}
	printf("%d\n",ansn);
	for (int i=1; i<=ansn; i++)
		for (int j=0,sz=ans[i].size(); j<sz; j++) printf("%d%c",ans[i][j],j==sz-1?'\n':' ');//ans[]=a,b,c,d
	return 0;
}

input&output:
(虽然不知道结果怎么来到但我相信没毛病→v→)
在这里插入图片描述

10.含变量排序问题(待解决)
原题:https://ac.nowcoder.com/acm/contest/11256/J
问题:有n个珠宝在海中(x,y,z)位置(z轴正方向竖直向下),以每秒v的速度下沉,你从(0,0,0)的位置,每秒可以花费d2(d为你与某珠宝的距离)力量捞起某宝石,求把全部宝石捞完的最小力量。(n<300,x,y,z<1000)
算法类型:new
时间复杂度:o(约n3)
空间复杂度:o(3n)
算法思路:这题很容易被误认为是用贪心算法:拿v排序的在最底层,拿下一步最大距离变化量排序的在第二层,通过算式求导求距离最大变化率的在第三层。这些的贪心都只能做到局部最优,并不是全局最优(比如第一层v小但z大d变化就大,第二、三层拿走当前变化(率)最大的没有拿走下一步变化(率)最大的)。那怎么办呢?我们先从问题出发,再去选用合适的方法:求最小的力量,那么ans=x12+y12+(z1+v1t1)2+x22+y22+(z2+v2t2)2+……+xn2+yn2+(zn+vntn)2,整理后得ans=(x12+……+xn2)+(y12+……+yn2)+(z12+……+zn2)+(2z1v1t1+v12t12+2z2v2t2+v22t22+……+2znvntn+vn2tn2),也就是说求ans最小,就要让后的最小。

#include<cstdio>
#include<algorithm>
const int maxn=300+5;
typedef long long ll;
struct jew{
    int z,v,id;
};
jew g[maxn];
int n;
ll ass=0;
ll f(int p,jew x){
    p--;
    return 1ll*p*p*x.v*x.v+2ll*p*x.v*x.z;
}
void sofa(int x){
    int j;
    for(int i=1;i<=n;i++){
        if(g[i].id==x){
            j=i;
            break;
        }
    }
    for(int i=1;i<=n;i++){
        if(f(i,g[i])+f(j,g[j]) > f(i,g[j])+f(j,g[i])){
            printf("%d %d\n",i,j);
            std::swap(g[i],g[j]);
            sofa(g[j].id);//L
            sofa(x);//R
            return;
        }
    }
}
int main(){
    scanf("%d",&n);
    int x,y,z,v;
    for(int i=1;i<=n;i++){
        scanf("%d%d%d%d",&x,&y,&z,&v);
        ass+=1ll*x*x+1ll*y*y+1ll*z*z;
        g[i]=(jew){z,v,i};
    }
    sofa(1);
    for(int i=1;i<=n;i++)ass+=f(i,g[i]);
    printf("%lld\n",ass);
    return 0;
}

仲舟原创,未经允许不得转载。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值