ZOJ 3872 浙江2015年省赛试题

博客介绍了ZOJ 3872竞赛题目,该题要求计算数组所有连续子数组的美丽度之和。作者首先探讨了使用无算法或动态规划的可能性,但由于数据规模,尝试无算法的方法导致重复计算。最终,作者采用动态规划解决此问题,通过定义以每个元素为结尾的子序列和,并避免重复计算,实现了有效的状态转移。博客提供了错误的无算法代码示例及正确的动态规划代码片段。
ZOJ Problem Set - 3872
Beauty of Array

Time Limit: 2 Seconds      Memory Limit: 65536 KB

Edward has an array A with N integers. He defines the beauty of an array as the summation of all distinct integers in the array. Now Edward wants to know the summation of the beauty of all contiguous subarray of the array A.

Input

There are multiple test cases. The first line of input contains an integer T indicating the number of test cases. For each test case:

The first line contains an integer N (1 <= N <= 100000), which indicates the size of the array. The next line contains N positive integers separated by spaces. Every integer is no larger than 1000000.

Output

For each case, print the answer in one line.

Sample Input
3
5
1 2 3 4 5
3
2 3 3
4
2 3 3 2
Sample Output
105
21
38
 

Author: LIN, Xi

Source: The 12th Zhejiang Provincial Collegiate Programming Contest


        看到问类似于这样求方案数目、出现次数的题目,马上想到的是搜索、贪心、无算法、或动态规划。一看数据十万,那就只能是贪心、无算法或者是动态规划。这道题也不是求最优的,因此咱们考虑无算法或者是动态规划就行了。


        由于我这个人比较懒,所以先考虑不用算法能不能解决问题。如果我们把连续的相同的数字浓缩成一个带权的数字,然后看这个点被覆盖多少次,以及这个点内部可以有多少条线段,一求和不久完了吗?


        但是这样我们遭遇了一个问题:容易重复计算。也就是说一条覆盖当前区域的线段,很有可能同时覆盖了下一个没有考虑过的区域。然后在考虑那个区域的时候我们自然而然又计算了一次。


        一下是“无算法”的错误代码。可以说这个错误还是挺不好找的,如果真的陷入这里了恐怕非得WA一发不可。


       

#include<iostream>
#include<string.h>
using namespace std;

long long int a[1000005];
long long int b[1000005];
long long int pre[1000005];
long long int sum[1000005];

int main()
{
	int T;
	cin>>T;
	while(T--)
	{
		memset(b,0,sizeof(b));
		memset(pre,0,sizeof(pre));
		int n;
		cin>>n;
		int cur = 0,p = -1;
		int t;
		for(int i = 1;i<=n;i++)
		{
			cin>>t;
			if(t != p)
			{
				cur ++;
				a[cur] = t;
				b[cur] ++;
				p = t;
			}
			else
			{
				b[cur]++;
			}
		}
		for(int i = 1;i<=cur;i++)
		sum[i] = sum[i-1] + b[i];
		long long int ans = 0;
		for(int i = 1;i<=cur;i++)
		{
			ans += a[i] * ((sum[i-1] - pre[a[i]]) * (sum[cur] - sum[i-1]) + b[i] * (sum[cur] - sum[i]) + b[i] * (b[i] + 1) / 2);
			pre[a[i]] += b[i];
		}
		cout<<ans<<endl;
	}
	return 0;
}

        看来只能上动态规划了。偷懒失败。因为数据量的问题我们恐怕也只能定义一种状态了:以第i个数为结尾如何如何。稍微想想就能知道应该定义成以第i个数结尾的所有子序列的和是多少。那么这样我们就有了一个很简单的转移方法:


        dp[i] = dp[i-1] + x[i] * (i - last[x[i]]);


        last表示的是x[i]这个数字上一次出现在哪个位置,这样可以确保不会重复计算。由于dp[i]只和dp[i-1]有关系,所以我们也可以压缩一下,把一个数组压缩成一个变量进行转移就得了。每次转移之后更新一下总的和就ok。上简短小代码:


       

#include<iostream>
#include<cstring>
#include<string>
#define MAXN 1000005
using namespace std;
int last[MAXN];

int main()
{
    int T, n;
    cin>>T;
    while (T--)
    {
        memset (a, 0, sizeof (a));
        cin>>n;
        int t;
		long long dp = 0, sum = 0;
        for (int i=1; i<=n; ++i)
        {
            scanf ("%d", &t);
            dp = (i - last[t]) * t + dp;
            sum += dp;
            last[t] = i;
        }

        cout<<sum<<endl;
    }
    return 0;
}

        这次教训告诉我,以后就别偷懒了。把所有的思路都想完看看哪个更靠谱再敲吧。这么个水题居然浪费了那么长时间简直是丧心病狂。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值