Codeforces Round #193 (Div. 2) ABCD

悲剧的一场比赛,感觉败在英语上了,题意理解错误题目肯定做不出来。

第一题看懂就可以直接写:

A:

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<cstdlib>
#include<queue>
#include<algorithm>
#include<stack>
#include<deque>
#include<set>
#include<vector>
#include<iomanip>
#include<cctype>
#include<string>
#include<memory>
#include<map>
#include<sstream>
#define mem(a) memset(a, 0, sizeof(a))
#define Forr(n) for (int ii = 0; ii < n; ++ii)
#define sl(a) strlen(a)
typedef long long LL;
typedef double dou;
const int Mod = 1000000007;
const double eps = 1e-8;
const LL inf = (LL) (1e18 + 0.5);
const int inf1 =(LL) (1e9 + 0.5);
const int N = 100005;
using namespace std;

int main()
{
        LL n, i, j, k, re = 0;
        char s[2005] = {0};
        cin >> n;
        cin >> s;
        for (i = n; i < sl(s); i += n)
        {
                if (s[i - 1] == s[i - 2] && s[i - 2] == s[i - 3])
                        re++;
        }
        cout << re << endl;
        return 0;
}

B显然是一个动态规划,怎么DP呢?一开始的想法是O(n)算出长度为k的所有区间和s[n - k],每个s[i]和自己区间最左边的下标组成一个结构体,然后降序排序这样和最大的在左边,下标 0 记为st,只需要从左往右找到和它下标差大于等于k的第一个s[i]即可这个下表 i 记为en,然后只需在st,en中再查找和比s[st] + s[en] 大的两个数,更新st,en,最后结果是p[st].n,p[en].n。代码如下:

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<cstdlib>
#include<queue>
#include<algorithm>
#include<stack>
#include<deque>
#include<set>
#include<vector>
#include<iomanip>
#include<cctype>
#include<string>
#include<memory>
#include<map>
#include<sstream>
#define mem(a) memset(a, 0, sizeof(a))
#define Forr(n) for (int ii = 0; ii < n; ++ii)
#define sl(a) strlen(a)
typedef long long LL;
typedef double dou;
const int Mod = 1000000007;
const double eps = 1e-8;
const LL inf = 1e18;
const int inf1 = 1e9;
const int N = 200005;
using namespace std;
LL a[N], s[N];

struct pp
{
		LL sum, n;
}p[200005];

bool cmp(pp a, pp b)
{
		if (a.sum == b.sum)
				return a.n < b.n;
		return a.sum > b.sum;
}

LL search(LL n, LL en, LL k)
{
		LL i;
		for (i = n + 1; i < en; ++i)
				if (abs(p[n].n - p[i].n) >= k)
						return i;
		return -1;
}

int main()
{
		LL n, i, j, k, st, en, max, tem;
		cin >> n >> k;
		for (i = 0; i < n; ++i)
				cin >> a[i];
		for (i = 0; i < k; ++i)
				s[0] += a[i];
		p[0].sum = s[0];
		p[0].n = 0;
		for (i = 1; i <= n - k; ++i)
		{
				s[i] = s[i - 1] - a[i - 1] + a[i + k - 1];
				p[i].sum = s[i];
				p[i].n = i;
		}
		sort(p, p + n, cmp);
		st = max = 0;
		en = n - 1;
		for (i = 0; i < n; ++i)
				if (abs(p[0].n - p[i].n) >= k)
				{
						en = i;
						max = p[0].sum + p[i].sum;
						break;
				}
		for (i = 1; i < en; ++i)
		{
				tem = search(i, en, k);
				if (tem > 0)
				{
						if (p[i].sum + p[tem].sum > p[st].sum + p[en].sum)
						{
								st = i;
								en  = tem;
						}
				}
		}
		if (p[st].n > p[en].n)
		{
				tem = st;
				st = en; 
				en = tem;
		}
		cout << p[st].n + 1 << ' ' << p[en].n + 1 << endl;

		return 0;
}
很不幸,T,原因很明显,这个算法在坏数据下是退化为O(n^2)的,题目的数据规模200000,显然T。

想想别的方法吧,需要一个O(n)的算法,这个问题是个求最大值的问题,如果对于每个下标为 i 开始长度为k的区间,如果知道了在下标为i +k之后的所有长度为k的区间的和的最大值,那么,让i从0,跑到n-k,不断更新st,en,即可,可是如何在O(n)时间内算出下标i+k之后的区间里s的最大值呢?如果从左往右算的话显然是O(n^2)显然不行,但是这其实是个很经典的问题啊,(这次我一定记住了!)我们可以从右往左,不断更新最大值,如果新加的数比原来下标时的最大值小,则最大值不变,否则更新最大值,赋值给它,这样就做到O(n)啦!!!

代码如下:

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<cstdlib>
#include<queue>
#include<algorithm>
#include<stack>
#include<deque>
#include<set>
#include<vector>
#include<iomanip>
#include<cctype>
#include<string>
#include<memory>
#include<map>
#include<sstream>
#define mem(a) memset(a, 0, sizeof(a))
#define Forr(n) for (int ii = 0; ii < n; ++ii)
#define sl(a) strlen(a)
#pragma warning (disable : 4996)
typedef long long LL;
typedef double dou;
const int Mod = 1000000007;
const double eps = 1e-8;
const LL inf = 1e18;
const int inf1 = 1e9;
const int N = 200005;
using namespace std;
LL a[N], s[N], ma[N], ind[N];

int main()
{
		LL n, i, j, k, st, en, tem;
		cin >> n >> k;
		for (i = 0; i < n; ++i)
				scanf("%I64d", a + i);
		for (i = 0; i < k; ++i)
				s[0] += a[i];
		for (i = 1; i <= n - k; ++i)
				s[i] = s[i - 1] - a[i - 1] + a[i + k - 1];
		ma[n - k] = s[n - k];
		ind[n - k] = n - k;
		for (i = n - k - 1; i >= 0; i--)
		{
				if (s[i] >= ma[i + 1])
				{
						ma[i] = s[i];
						ind[i] = i;
				}
				else
				{
						ma[i] = ma[i + 1];
						ind[i] = ind[i + 1];
				}
		}
		st = 0, en = ind[k];
		tem = s[0] + s[ind[k]];
		for (i = 1; i <= n - 2 * k; ++i)
		{
				if (s[i] + s[ind[i + k]] > tem)
				{
						st = i;
						en = ind[i + k];
						tem = s[i] + s[ind[i + k]];
				}
		}
		cout << st + 1 << ' ' << en + 1 << endl;

		return 0;
}

AC,很开心!!!这种思想可以解决好多这样的求最值问题,可以使最小值等。

C:

贪心,先按b升序同时a升序排序,那么前p-k个一定不可能被选,然后从其他方案中选择这是保证a最大,最后这个最大值显然是最优的,然后调整b最大,这是要和最大值集合里b最小的方案比较,b比最小值还小的方案可能被选。代码如下:

#include <cstdio>
#include <cstdlib>

struct xxx
{
    int a,b,num,num2;
}x[111111];

int ans[111111],pp=0;

int comp1(const void *m,const void *n)
{
    return (*(struct xxx *)n).b-(*(struct xxx *)m).b;
}

int comp2(const void *m,const void *n)
{
    return (*(struct xxx *)n).a-(*(struct xxx *)m).a;
}

int comp3(const void *m,const void *n)
{
    return (*(struct xxx *)m).a-(*(struct xxx *)n).a;
}

int comp4(const void *m,const void *n)
{
    return (*(struct xxx *)m).num2-(*(struct xxx *)n).num2;
}

int main()
{
    int n,p,k;
    pp=0;
    scanf("%d%d%d",&n,&p,&k);
    for (int i=0;i<n;i++)
        scanf("%d%d",&x[i].a,&x[i].b),x[i].num=i+1;
    qsort(x,n,sizeof(x[0]),comp1);
    for (int i=0;i<n;i++)
    {
        int j;
        for (j=i+1;x[j].b==x[i].b;j++) ;
        qsort(x+i,j-i,sizeof(x[0]),comp3);
        i=j-1;
    }
    for (int i=0;i<n;i++)
        x[i].num2=i+1;
    qsort(x,n-(p-k),sizeof(x[0]),comp2);
    int i,j,max=0;
    for (i=k;i<n&&x[i].a==x[i-1].a;i++) ;
    for (j=k-2;j>=0&&x[j].a==x[j+1].a;j--) ;
    qsort(x+j+1,i-j-1,sizeof(x[0]),comp4);
    for (int i=0;i<k;i++)
    {
        ans[pp++]=x[i].num;
        if (x[i].num2>max) max=x[i].num2;
    }
    for (int i=k;i<n;i++)
        if (x[i].num2>max&&x[i].num2<=max+p-k)
            ans[pp++]=x[i].num;
    for (int i=0;i<pp;i++)
        printf("%d ",ans[i]);
    printf("\n");
    return 0;
}


D是一个图论边度数的计数,算两次思想,(考察每个点引出的边,再考察总体边的权值)很容易写出表达式,(理解错题意的话结果就不一样了。。。。T^T)读入数据用scanf,cin Tle了两次。注意考虑精度误差,技巧就是先乘以一个大数再最后除去,代码如下:

D:

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<cstdlib>
#include<queue>
#include<algorithm>
#include<stack>
#include<deque>
#include<set>
#include<vector>
#include<iomanip>
#include<cctype>
#include<string>
#include<memory>
#include<map>
#include<sstream>
#define mem(a) memset(a, 0, sizeof(a))
#define Forr(n) for (i = 0; i < n; ++i)
#define sl(a) strlen(a)
typedef long long LL;
typedef double dou;
const int Mod = 1000000007;
const double eps = 1e-8;
const LL inf = 1e18;
const int inf1 = 1e9;
const int N = 100005;
using namespace std;
dou x[2005];
int s[2005];

struct po
{
        LL n, sum;
}p[200005];


dou mul(LL n, LL N, LL k)
{
        LL i;
        if (s[n] == 0)
        {
                dou a = (dou) n, b = (dou) N, c = (dou) k, d = 1.0;
                for (i = 0; i < k; ++i)
                        d *= ((a - (dou) i) / (b - (dou) i));
                x[n] = d * c / a;
                s[n] = 1;
                return x[n];
        }
        else
                return x[n];
}

int main()
{
        LL n, i, j, k, x;
        double y = 0;
        LL sum = 0;
        mem(p);
        cin >> n >> k;
        for (i = 0; i < n; ++i)
                for (j = i + 1; j < n; ++j)
                {
                        scanf("%I64d", &x);
                        if (x != -1)
                        {
                                p[i].n++;
                                p[j].n++;
                                p[i].sum += x;
                                p[j].sum += x;
                        }
                }
        for (i = 0; i < n; ++i)
        {
                if (p[i].n >= k)
                        y += (mul(p[i].n, n, k) * (dou) p[i].sum * 10000.0);
        }
        cout << (LL) ((y + 0.5) / 10000.0) << endl;
        return 0;
}

D的CF解题指导上给了一个图论定理,k>= 3时满足题意条件的只有完全图,直接算,所以只需要计算k=1,2。这样也是可以的,以上解法没有用这个知识,我觉得更好一点吧。



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值