[LCC *a* Round 1] 题解+随笔

[LCC a Round 1] 题解 蒟蒻陈大爷的~~

A- lxn的OJ 题目网址

翻译

题目背景

神犇lxn有她自己的OJ啦~
身为一个非常强的信竟教练,她经常在lxnOJ上面出一些膜您赛。蒟蒻lxd为了提升自己的信竟实力,于
是就去打lxnOJ上的比赛,结果发现自己菜的抠jio,就有了下面的这个题目

题目描述

lxnOJ上的膜您赛共有n 题,序号分别为 1-n 。lxd共提交了m 次。第i 次他提交了第ki道题,lxnOJ返回了ri的评测结果。由于lxnOJ刚建站,所以 只可能是字符串 AC 或是字符串 WA 中的一种.
lxnOJ的赛制比较特别。==当且仅当lxd对于ki 这道题第一次获得 AC 的结果时,该题前面所有获得 WA 的次数全部计入提交错误次数y 中,并把该题计入到正确题数x 中。==对于其后的提交均不计入到 x或y 中。
下面给出一场lxd打lxnOJ膜您赛的评测记录,请帮lxd求出在本场比赛中他的正确题数 x与提交错误次数y分别是多少。

思路

这还用得着思路, 水题啦。。。。
既然你们那么愿意我就勉强梳理一下吧, 谁叫我是大佬(菜鸡)呢 ,QAQ~~;
1.AC后的题都不再统计次数
2.一道题多次AC只算一次
3.WA理应就是AC前的次数啦
不管了,上代码:

代码

   #include
#include
#include
#include
#include
using namespace std;
const int maxn = 1e5 + 10;
int a[maxn];
map<int,int>maps;//题号 --> AC前的WA次数
map<int,int>::iterator it;
int n,m;
int ac = 0,wa = 0;
int main(void) {
    void solve();
    cin >> n >> m;
    ac = wa = 0;//预处理
    solve();
    return 0;
}
void solve() {
    if(m == 0) {//一次也没提交
        cout << 0 << " " << 0 << endl;
        return ;
    }
    int num;
    string vis;
    for(int i = 1; i <= m; i ++) {
        cin >> num >> vis;
        if(maps[num] == -1) continue;//AC了
        if(vis == "AC") {
            wa += maps[num];
            ac += 1;
            maps[num] = -1;
            continue;
        }
        maps[num] ++;//又错了一次;
    }
    maps.clear();
    cout << ac << " " << wa << endl;
    return ;

B-生而为赢 题目网址

这破题搞得我一脸懵逼。。。

翻译

题目描述

蒟蒻lxd特别喜欢玩一款叫做屁股肉的音游。
lxd已经打完了屁股肉的 首曲子,第i 首歌属于ti 个专题,lxd在这首歌中获得了di 的分数。
有一天,lxd要参加一个比赛,这个比赛要求选手选出 k首自己打过的歌来计分参与排名。计分规则如下:
选手分数为“专题涉猎分”和“谱面分”两部分之和。
“谱面分”等于每个选手所选出的 k首歌中所获分数的总和。
“专题涉猎分”等于k 首歌中不同专题的专题数 的平方。
下面给出lxd的游戏记录,你能计算出按此规则lxd的最高得分是多少吗?

思路

显而易见, 我们可以很容易的考虑出最优的曲子: 作为一个新的专题且分数很高;
因为==每个选手所选出的 k首歌中所获分数的总和+k 首歌中不同专题的专题数 的平方。==他作为一个新的专题, 而且谱面分也高。 所以, 我们就可以把歌曲按di排序;
选出第一次出现的来,同时对于这个专题第二次出现的进行判断(放入栈中),
如果新加一个专题的平方 > 当前谱面分值差的话, 就替换。。。

代码


#include
using namespace std;
struct node
{
    int x,y;
    bool operator<(const node&o)const
    {
        return y>o.y;
    }
}a[100005];
int n,k,cot=0;
bool vis[100005];

stack<int>s;
int main()//考虑先选, 后更新
{
    long long ans=0,res=0,type=0;
    scanf("%d%d",&n,&k);
    for(int i=1;i<=n;i++)scanf("%d%d",&a[i].x,&a[i].y);
    sort(a+1,a+1+n);
    for(int i=1;i<=n;i++)
    {
        if(cot<k)//没选到k个数
        {
            if(!vis[a[i].x])//第一次选
            {
                vis[a[i].x]=true;
                type++;
            }
            else s.push(a[i].y);//否则放进队列待更新

            res+=a[i].y;
            cot++;
            ans=max(ans,res+type*type);//公式
        }
        else//更新第二次出现的点
        {
            if(s.empty())break;
            if(vis[a[i].x]) continue;
            vis[a[i].x]=true;
            type++;
            res+=a[i].y;
            res-=s.top();s.pop();
            ans=max(ans,res+type*type);//公式
        }
    }
    cout<<ans<<endl;
}

我懒得解释了。。。以后再补

C- 盘他 题目网址

翻译

题目描述

蒟蒻lxd因为CSP2019考崩了十分伤心。为舒缓身心,冷dalao给了lxd一个由 n个整数组成的数组。
lxd每次可以选择这个数组里面任意两个元素拿出来盘一会变成一个新元素后再放到数组里的任意一个位置中。这个新元素的值与lxd拿出来的两个元素的数值之和相同。这一步骤我们称之为“盘”。比如对数组 [2,1,4] 盘一次后可以得到 [3,4] 或 [1,6] 或 [2,5] 。 冷dalao有点担心这个数组会被lxd盘坏,所以你需要找出在数组 被盘了任意次后(可能为0次)数组中最多有几个可被3整除的元素。

思路

注意到:任意次!!!woc (不能骂人…)那我怎么盘都可以的话。。。 不就相当于问这个数列里合并一些数, 最多有多少个数被3整除吗;
整除。。。。对了, 看余数!!!
因此我们就把数列中的数分成余数为1的,余数为2的, 和余数为0的;
合并有以下几种方式:
1.三个余数为一的
2. 三个余数为二的
3. 余数为一和二的各一个

优先选择次数少的

上述题目证明及普遍规律
[LCC *a* Round 1] 题解+随笔_第1张图片

代码

#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include

using namespace std;

int t;
int n;
int d[110];
int a, b, c;
int main()
{
	cin >> t;
	while(t--)
	{
		cin >> n;
		a = b = c = 0;
		for(int i = 1; i <= n ; i++)
		{
			cin >> d[i];
			int haha = d[i] % 3;
			if(haha == 0)
			{
				a++;
			}
			if(haha == 1)
			{
				b++;
			}
			if(haha == 2)
			{
				c++;
			}
		}
		int part1 = min(b, c);
		int part2 = 0;
		part2 += (b - part1) / 3;
		part2 += (c - part1) / 3;
		cout << part1 + part2 + a << endl;
	}
	return 0;
}
简单的统计, 不赘述了。。。。

D-陈大爷的晨跑~臭lxd, 出个破题还来坑我~ 原题网址

翻译

题目背景

lxn作为优秀的教练,立志于培养心向美好、体魄强健的Oier。当lxn看到陈大爷的体型后便主动开始督
促陈大爷每天晨跑补习体育…(我无语了QAQ…)

题目描述

lxn让陈大爷在wfyz的小树林里晨跑。(为什么是小树林???)我们可以把小树林看成一个由n 个节点连成的无向图。小树林中有m条每条长度为wi 的土路连接 ui与vi ,同时还有k 条每条长度为yi 的石板路将点1与点 si连接起来。lxn让陈大爷从点 跑到点 再跑回点 从它跑到点 ……如此循环直到跑到点 为止。机智的陈大爷马上想到跑最短路径可以让自己不那么累。(哇塞, 我好聪明)
但lxn早已预料到这一点,她让djh拿路障堵住尽可能多的石板路从而使得陈大爷没法延着陈大爷预想的最短路径晨跑。然而djh与陈大爷有着真挚的友谊,他决定只堵住那些放了路障之后对于陈大爷预想的最短路径长度毫无影响的石板路。请问最多能堵住多少条石板路
(呵呵呵呵呵呵呵呵呵呵呵呵呵呵, woc)
¥%%¥&%…%$&&&>>>>>^#@& (口吐芬芳了… 谅解)

思路

从s点向其他点跑最短路, 因为是堵最短路, 所以就拿最短路跟他比较
无非就两种情况::
1.dist[x] < s[x];
2.dist[x] = s[x];
如果第一种, 那么怎么堵也没有问题;
如果第二种, 判断, 如果他是唯一的最短路, 那么就不能堵

优化

可以在跑最短路时判断被松弛次数。。。。。。

样例解释

sample 1

5 5 3
1 2 1
2 3 2
1 3 3
3 4 4
1 5 5
3 5
4 5
5 5

1
2
3
4
5
5stone
5stone
5stone
1
2
3
4
5

(流程图强行转化为图论/苦笑)

  1. 对于到3的石板路, 最短路为3, 然而石板路长度是5, 可以堵路;
  2. 对于到4的石板路, 最短路为5,就是石板路, 不可堵;
  3. 对于到5的石板路, 最短路为5, 即使石板路, 也是土路, 所以可以跑土路, 堵石板路.
    共计2条可堵
    样例二略。。。(sorry啊, 太累了。。。。)

代码

#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
#define ll long long
#define usll unsigned ll
#define mz(array) memset(array, 0, sizeof(array))
#define minf(array) memset(array, 0x3f, sizeof(array))
#define REP(i,n) for(i=0;i<(n);i++)
#define FOR(i,x,n) for(i=(x);i<=(n);i++)
#define RD(x) scanf("%d",&x)
#define RD2(x,y) scanf("%d%d",&x,&y)
#define RD3(x,y,z) scanf("%d%d%d",&x,&y,&z)
#define WN(x) prllf("%d\n",x);
#define RE  freopen("D.in","r",stdin)
#define WE  freopen("1biao.out","w",stdout)
#define mp make_pair
#define pb push_back

const ll INF=1LL<<60;

const int maxn=111111;
const int maxm=888888;
struct edge {
    int v,next;
    ll w;
} e[maxm];///边表
int head[maxn],en;

void add(int x,int y,ll z) {
    e[en].w=z;
    e[en].v=y;
    e[en].next=head[x];
    head[x]=en++;
}

int n,m,k;
ll g[maxn];
bool f[maxn];///入队标志
int b[maxn], c[maxn];
ll d[maxn];///b为循环队列,d为起点到各点的最短路长度
void spfa() { ///0~n-1,共n个点,起点为st
    int i,k;
    int st=0, l=0, r=1;
    memset(f,0,sizeof(f));
    memset(b,0,sizeof(b));
    for(i=0; i<n; i++)
        d[i]=INF;
    b[0]=st;
    f[st]=1;
    d[st]=0;
    c[st]=1;
    while(l!=r) {
        k=b[l++];
        l%=n;
        for(i=head[k]; i!=-1; i=e[i].next)
            if (d[k]+e[i].w < d[e[i].v]) {
                d[e[i].v]=d[k] + e[i].w;
                c[e[i].v]=c[k];
                if (!f[e[i].v]) {
                    if(d[e[i].v]>d[b[l]]) {///SLF优化,这题卡没优化的SPFA……
                        b[r++]=e[i].v;
                        r%=n;
                    } else {
                        l--;
                        if(l==-1)l=n-1;
                        b[l]=e[i].v;
                    }
                    f[e[i].v]=1;
                }
            } else if(d[k]+e[i].w == d[e[i].v])
                c[e[i].v]+=c[k];
        f[k]=0;
    }
}

void init() {
    memset(head,-1,sizeof(head));
    en=0;
}

int main() {
    int i,x,y;
    ll z;
    while(scanf("%d%d%d",&n,&m,&k)!=EOF) {
        init();
        REP(i,m) {
            scanf("%d%d%I64d",&x,&y,&z);
            x--;
            y--;
            add(x,y,z);
            add(y,x,z);
        }

        REP(i,n) g[i]=INF;

        REP(i,k) {
            scanf("%d%I64d",&x,&z);
            x--;
            if(z<g[x]) g[x]=z;
        }

        REP(i,n)
            if(g[i]!=INF) {
                add(0,i,g[i]);
                add(i,0,g[i]);
            }

        memset(c,0,sizeof(c));
        spfa();

        int remain=0;
        REP(i,n)
            if(g[i]!=INF && c[i]==1 && d[i]==g[i])
                remain++;
        printf("%d\n",k-remain);
    }
    return 0
}

E-好数 原题网址

翻译

题目描述

神犇zyd特别喜欢能被3的不同的自然数次幂之和所表示出来的数,他把这种数称之为好数。我们来举点栗子:
30是个好数,因为 30 = 33 + 31;
1也是个好数,因为1 = 30
12更是个好数,因为12 = 33+31(知道我打次幂多难吗呜呜呜呜呜。。。)
但2不是个好数,因为它没法表示为。3不同的自然数次幂之和
因为上面的原因,19也不是个好数(比如 19 = 32 + 32 + 30 或 19 = 3 2 + 3 1 + 3 1 + 3 0都不是好的拆分方式)20更不是个好数(比如20 = 32 + 3 2 + 3 0 + 3 0 就不是一种合法的拆分方式)
诚然,除了上面举的例子外19和20存在其他的拆分方式,但他们都无法表示为3的不同的自然数次幂之和。
有一天,zyd找到了lxd,给了他n个数, 对于每一个数ni, 使得有一个好数mi尽可能地小而且要大于ni, 众所周知, lxd是个大菜逼, 请你帮帮他吧!;(嗯, 他就是菜逼)

思路

3不同的自然数次幂之和 … 那么其他的次幂就可以形象成加上了0…对了!!
!! 就是相当于三进制拆分吗!!!
不同。。。就可以说明三进制数上不能是2, 只能是0或1;
那如果是2怎么办呢…

进位啊, 因为你要求比原来的数要大, 而且要最小化, 所以你可以把遇到二的那一位+
1, 从有二的那一位到各位全都变成零!!!
如果+1的那一位也变成2了, 咋办??

简单!!, 循环嘛!!!

细节看代码。。。

代码


#include
using namespace std;
typedef long long int ll;
const ll MAXN=2e5+51;
ll test,tot,kk,ptr,res;
ll num[MAXN];
inline ll read()
{
    register ll num=0,neg=1;
    register char ch=getchar();
    while(!isdigit(ch)&&ch!='-')
    {
        ch=getchar();
    }
    if(ch=='-')
    {
        neg=-1;
        ch=getchar();
    }
    while(isdigit(ch))
    {
        num=(num<<3)+(num<<1)+(ch-'0');
        ch=getchar();
    }
    return num*neg;
} 
inline void solve()
{
    kk=read();
    memset(num,0,sizeof(num)),tot=0,res=0;
    while(kk)
    {
        num[++tot]=kk%3,kk/=3;
    }
    for(register int i=tot;i;i--)
    {
        if(num[i]==2)
        {
            num[i]=0,ptr=i+1;
            for(register int j=i-1;j;j--)
            {
                num[j]=0;
            }
            while(num[ptr]==1)
            {
                num[ptr++]=0;
            }
            num[ptr]=1,tot=max(tot,ptr);
        }   
    } 
    for(register int i=tot;i;i--)
    {
        res=(res*3)+num[i];
    }
    printf("%lld\n",res);
}
int main()
{
    test=read();
    for(register int i=0;i<test;i++)
    {
        solve();
    }
}

到这里, 题目就全写完啦!!!!!
这是本蒟蒻的第一篇博客, 还请大家多多谅解
以后会经常写的, 请大家常来关注, 帮我找茬, 谢谢!!!

你可能感兴趣的:(比赛)