Contents
- Contents
- 六元组
- Description
- Input
- Output
- 数据范围与约定
- Solution
- 牛排序
- Description
- Input
- Output
- 样例解释
- Solution
- Step1
- Step2
- Step3
- Step4
- 打砖块
- Description
- Input
- Output
- 数据范围与约定
- Solution
- 六元组
1.六元组
(six.c/.cpp/.pas)
Description
有n个整数,现在想知道有多少个六元组(a,b,c,d,e,f)
满足:(a × b + c) ÷ d – e = f
Input
输入文件名为(six.in)。
第一行:n(1<=n<=10)
第二行n个数ai(-30000<=ai<=30000)
Output
输出文件名为(six.out)。
输出这样的六元组的个数
2
2 3 4
数据范围与约定
对于30%的数据,1 <= n <= 10
对于100%的数据,1 <= n <= 100
Solution:
刚开始以为题意是:给定的ai就是a,所以要找出满足所有ai的b,c,d,e,f
鬼能做出来啊。。
最后才理清了题意,a,b,c,d,e,f就是n个数的任意一个,只要满足要求就好。这样就变得十分简单了。但是看数据范围就可以知道,六维的枚举只能得30分,而要想得100分就必须压到至多三维。但这样也并不麻烦,化简可以得到
a*b+c=d*(e+f)这样,就可以同时枚举三个数a[i],a[j],a[k]。假如把等式左边的数放到数组l[],右边放到r[]中,如果l[]中的一个数在r[]中出现过,累加次数就是最后的答案了。
但是需要注意的是,不能直接枚举,这样会TLE,可以用STL中的map或者multiset,但事实证明后者不可行。。同样超时。下面是两种方法的代码:
Code1【map】:
#include <stdio.h>
#include <string.h>
#include <map>
#define MAXN 1001000
typedef long long ll;
using namespace std;
int n,ans;
int a[110],l[MAXN],r[MAXN];
map<int,int>mp;
int main()
{
freopen("six.in","r",stdin);
freopen("six.out","w",stdout);
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
}
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
for(int k=1;k<=n;k++)
{
mp[a[i]*a[j]+a[k]]++;
}
}
}
for(int i=1;i<=n;i++)
{
if(!a[i])
continue;
for(int j=1;j<=n;j++)
{
for(int k=1;k<=n;k++)
{
int x=a[i]*(a[j]+a[k]);
ans+=mp[x];
}
}
}
printf("%d\n",ans);
return 0;
}
Code2:【multiset】
#include <stdio.h>
#include <string.h>
#include <set>
#define MAXN 1001000
typedef long long ll;
using namespace std;
ll n,ans;
ll a[110],l[MAXN],r[MAXN];
multiset<int>s;
int main()
{
freopen("six.in","r",stdin);
freopen("six.out","w",stdout);
scanf("%lld",&n);
for(ll i=1;i<=n;i++)
{
scanf("%lld",&a[i]);
}
ll cnt_l=0,cnt_r=0;
for(ll i=1;i<=n;i++)
{
for(ll j=1;j<=n;j++)
{
for(ll k=1;k<=n;k++)
{
s.insert(a[i]*a[j]+a[k]);
}
}
}
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
for(int k=1;k<=n;k++)
{
ll x=a[i]*(a[j]+a[k]);
if(s.count(x)!=0)
{
ans+=s.count(x);
}
}
}
}
printf("%lld\n",ans);
return 0;
}
2.牛排序
(cowsort.c/.cpp/.pas)
Description
农夫JOHN准备把他的 N(1 <= N <= 10,000)头牛排队以便于行动。因为脾气大的牛有可能会捣乱,JOHN想把牛按脾气的大小排序。每一头牛的脾气都是一个在1到100,000之间的整数并且没有两头牛的脾气值相同。在排序过程中,JOHN可以交换任意两头牛的位置。因为脾气大的牛不好移动,JOHN需要X+Y秒来交换脾气值为X和Y的两头牛。
请帮JOHN计算把所有牛排好序的最短时间。
Input
输入文件名为(cowsort.in)。
第1行: 一个数N。
第2~N+1行: 每行一个数,第i+1行是第i头牛的脾气值。
Output
输出文件名为(cowsort.out)。
第1行: 一个数,把所有牛排好序的最短时间。
3
2
3
1 7
样例解释
队列里有三头牛,脾气分别为 2,3, 1。
2 3 1 : 初始序列
2 1 3 : 交换脾气为3和1的牛(时间=1+3=4).
1 2 3 : 交换脾气为1和2的牛(时间=2+1=3).
Solution:
刚开始以为这道题跟快排好像啊,然后就手写了一个快排,然后加上变化的脾气值。但是想想就知道了,这样只维护了交换次数最少,但不一定是交换的总价值和最小。
唉(????)??,还是介绍一下正解吧:
一共分为4步:
Step1:
Two Example
8 4 5 3 2 7 | 1 8 9 7 6 |
---|---|
2 3 4 5 7 8 | 1 6 7 8 9 |
第一步,很简单了~单独排个序。
Step2:
可以感受一下,(3 4 5)(8 2 7)是第一组数据的两个循环;(1)(8 9 7 6)是第二组数据的两个循环。学名叫做置换群(这样更好理解?)
所以我们的第二步就是找到所谓的置换群。
Step3:
通常在一个循环中,用min置换其余的所有数一定为最优方案。总代价为:
sum-min+(len-1)*min=>sum+(len-2)*min;
Step4:
但是如1 8 9 7 6上述方法并不成立,与其令(1)单独成为一组不如把6替换出来。也就是(6)(1 8 9 7)这样的代价为sum+min+(len-1)*smallest;
因此对于每一个循环来说,ans+=min{sum+(len-2)*min ,sum+min+(len-1)*smallest}
Code:
#include <stdio.h>
#include <string.h>
#include <algorithm>
#define INF 9999999
#define MAXN 1000000
using namespace std;
struct node
{
int len;
int mi;
int sum;
}cir[MAXN];
int n,cnt,ans;
int a[MAXN],tmp[MAXN],pos[MAXN],visit[MAXN];
int cmp(int a,int b){return a<b?1:0;}
int min(int a,int b){return a<b?a:b;}
void pre_init()
{
for(int i=1;i<=n;i++)
{
cir[i].mi=INF;
}
}
int find(int x)
{
int l=1,r=n;
while(l<r)
{
int mid=(l+r)>>1;
if(tmp[mid]>=x)
{
r=mid;
}
else l=mid+1;
}
return r;
}
int main()
{
//freopen("cowsort.in","r",stdin);
//freopen("cowsort.out","w",stdout);
scanf("%d",&n);
pre_init();
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
tmp[i]=a[i];
}
sort(tmp+1,tmp+1+n,cmp);
for(int i=1;i<=n;i++)
{
pos[i]=find(a[i]);
}
for(int i=1;i<=n;i++)
{
if(!visit[i]&&a[i]!=tmp[i])
{
visit[i]=1,cir[++cnt].len=1;
cir[cnt].sum+=a[i];
cir[cnt].mi=min(cir[cnt].mi,a[i]);
int t=i;
while(a[pos[t]]!=a[i])
{
t=pos[t];visit[t]=1;
cir[cnt].len++;cir[cnt].sum+=a[t];cir[cnt].mi=min(cir[cnt].mi,a[t]);
}
}
}
for(int i=1;i<=cnt;i++)
{
int t1=cir[i].sum+(cir[i].len-2)*cir[i].mi;
int t2=cir[i].sum+cir[i].mi+(cir[i].len+1)*tmp[1];
ans+=min(t1,t2);
}
printf("%d\n",ans);
return 0;
}
3.打砖块
(brike.c/.cpp/.pas)
Description:
在一个凹槽中放置了n层砖块,最上面的一层有n块砖,第二层有n-1块,……,最下面一层仅有一块砖。第i层的砖块从左至右编号为1,2,……,i,第i层的第j块砖有一个价值a[i,j](a[i,j]<=50)。下面是一个有5层砖块的例子:
如果要敲掉第i层的第j块砖的话,若i=1,可以直接敲掉它,若i>1,则必须先敲掉第i-1层的第j和第j+1块砖。
你的任务是从一个有n(n<=50)层的砖块堆中,敲掉(m<=500)块砖,使得被敲掉的这些砖块的价值总和最大。
Input:
输入文件名为(brike.in)。
第一行为两个正整数,分别表示n,m,接下来的第i每行有n-i+1个数据,分别表示a[i,1],a[i,2]……a[i,n – i + 1]。
Output:
输出文件名为(brike.out)。
仅有一个正整数,表示被敲掉砖块的最大价值总和。
4 5
2 2 3 4
8 2 7
2 3
49 19
数据范围与约定
对于20%的数据,满足1≤n≤10,1≤m≤30
对于100%的数据,满足1≤n≤50,1≤m≤500
Solution:
直接粘题解吧:
发现同一行可以被打掉的砖块是可以无规律分布的,但每一列只能打掉从上往下连续的砖块,所以考虑按照每一列DP。
设计状态f[i][j][k]表示打到第i列时、当前这一列打掉从上往下连续j块砖、包括这一列打掉的这一列在内共计打掉了k块砖时,最大的价值总和。对于打掉砖的条件,是这块砖上面所有的砖都被打掉、以及前一列在这块砖上面的砖也都被打掉,前者在状态中已经表示了,后者可以在转移时加以限制。
对于计算每一列前j块砖的价值总和,前缀和处理后就可以O(1)得到了。
状态转移方程:f[i][j][k]=Max(f[i-1][p][k-j]+sum[i][j]),j-1≤p≤i-1,其中sum[i][j]表示第i列打掉前j块砖的价值总和。
Code:
#include <stdio.h>
#include <string.h>
int n,m,ans=-1;
int a[60][60],sum[60][60],s[50],f[60][60][550];
int max(int a,int b){return a>b?a:b;}
//f[i][j][k]打到第i列时,从上往下打掉了j块砖,共计打了k块砖的最大价值
//sum[i][j] 表示第i行打掉前j块砖的价值
int main()
{
//freopen("brike.in","r",stdin);
//freopen("brike.out","w",stdout);
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
{
for(int j=i;j<=n;j++)
{
scanf("%d",&a[i][j]);
sum[i][j]=sum[i-1][j]+a[i][j];
}
}
for(int i=1;i<=n;i++)
{
s[i]=s[i-1]+i;
}
for(int i=1;i<=n;i++)
{
for(int j=0;j<=i;j++)
{
for(int p=max(j-1,0);p<=i-1;p++)
{
for(int k=j+s[p];k<=m;k++)
{
f[i][j][k]=max(f[i][j][k],f[i-1][p][k-j]+sum[j][i]);
ans=max(ans,f[i][j][m]);
}
}
}
}
printf("%d\n",ans);
return 0;
}
版权声明:本文为博主原创文章,未经博主允许不得转载。