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;
}
仲舟原创,未经允许不得转载。