镇中7日做题小结 day1

好久不见!虽然没有多少人见过我
upd:10.31 20:20 临走前一天

day1 10.26

Divide by Three 阳光明媚的早上,怀着新鲜兴奋的战意,我们敲了一上午的模拟(突然被人捂嘴暴打)

我好好说,咳!美丽数是一个不含前导0,是3的倍数且位数大于零的正整数。举个例子,0、99、10110都是美丽数,但00、03、122都不是。一个正整数\(n\).它有不超过\(10^5\)位.用最小的步数把它改成一个美丽数。

费劲心思写模拟,看到题解才发现是数位dp!1位数字的时候特判,至于怎么输出结果的话,记录路径\(p[n][3]\)是个不错的选择.毕竟要想步数最小的话模拟还是有难度的,dp可以轻松完成

这里第几位数都是指下标从1开始,从源数字最高位开始数)

本题状态有,删到第几位数和是不是三的倍数(%3 余几),故设\(f[i][3]=k\)表示选到第i个最小步数,问题又来了,前导0怎么处理?

当我们面对困难时我们不要害怕(不要打我)

我们在讲,(当然如果有大佬给本蒟蒻在评论区给一点指点也是非常好的)

0尽可能不删,因为无论怎么删0,不会让她变成3的倍数,要删0的唯一情况就是,删除首位之后的前导0

我们是从前往后dp的啊,那如果前面的数字都被删了,自然这个0要删掉,如果前面有没被删的数字(假设这是第一个0)就当正常数字处理

注意 最后特判一下删过之后是否全为0或者说有没有0

转移方程,对\(f[i][j]\),首先你最多可以比\(f[i-1][0~3]\)多删1个数字,如果不删,那么直接从前面转移

于是就有了下面的代码:code time

    for(int i=2;i<=len;i++){
        int t=C(s[i]);
        for(int j=0;j<3;j++){
            f[i][j]=f[i-1][j]+1;  
            p[i][j]=j;            //now the choice is "j" amount to pre is also j
            if(s[i]==48&&f[i-1][j]==i-1)continue; 
            if(f[i][j]>f[i-1][(j-t+3)%3]){
                f[i][j]=f[i-1][(j-t+3)%3];
                p[i][j]=(j-t+3)%3;
            }
        }
    }
    if(f[len][0]==len){
        for(int i=1;i<=len;i++)if(s[i]==48)return cout<<0<<endl,0;
        return cout<<-1<<endl,0;
    }
    int pre;
    for(int i=len,j(0);i>=2;j=pre,i--){
        pre=p[i][j];
        if(f[i-1][pre]==f[i][j])ans[++n]=s[i];
    }

顺带说一下记录路径吧这玩意儿在背包九讲里也有虽然本蒟蒻并不是很深刻理解

我们用\(p[i][j]\)表示得到\(f[i][j]\)是从\(f[i-1][p[i][j]]\)转移过来的,即路径,或者说之前%3余p[i][j],现在%3余j

如上面

      f[i][j]=f[i-1][j]+1;  
      p[i][j]=j;            //now the choice is "j" amount to pre is also j

表明我们直接从把当前第i个数删了

那么如果没删,即\(f[i-1][pre]==f[i][j]\),在答案中这个数位就要输出出来

完结撒花!

NO2:The Great Mixing (这条应该能说清楚)(冷汗直冒)

\(k\)种可乐,第\(k\)瓶可乐的\(CO_2\)浓度是\(a_i/1000\),问要配置出浓度\(a_n/1000\)的可乐,最少需要几瓶可乐(\(a_i,a_n为整数\))

我们知道,如果\(\sum_{i=1}^{m}{a_i}/m*1000==n/1000\)那么\(\sum_{i=1}^{m}{a_i-n}==0\)这是一个非常重要的式子虽然她很平凡

再伟大的成就都有一个平凡的开始而不是一蹴而就

好了我们只要把数组范围往右挪1000,通过bfs,找一堆数进行表示,由于第一次出现的一定所用种数最少,若\(a[i-1000]\)表示混合出浓度为\(i/1000\)的最少种数,最后看\(a[1000]\)即可

复杂度,由于不会重复搜索,实际上大概\(O(n)\)

NO3.Michael and Charging Stations (瑟瑟发抖)

你每天会花\(a_i\)块钱去买东西,\(a_i=1000或2000\).如果你这次支付全用现金,那么你会获得\(a_i/10\)块钱信用金,或者你可以信用金和现金混用,这样不会获得信用金。问你n天最少要花多少钱。

首先肯定是信用金得到并花出去的越多越好

然而我却没有发现,这个题目是几乎不需要模拟的

无论\(a_i\)怎么排列,只有两种,总能凑到22000,并且我们总能买到20000元并用掉2000信用金,堪称完美

那么数据范围一下被缩小的很小······

你见过蚂蚁解决了大象吗?大概被剩下一点点数据难倒就只有我了。

不管怎样,每用10块钱可以得1块钱来一共支付11块钱,就是说我们最多可以省的钱是支付的钱的\(\frac{1}{11}\)

这么简单吗?不可能,我们让sum为剩下的总钱数(%22000之后) ,tmp表示能得到并花出去的信用金最大\(tmp=sum/11\),至于c++自己下取整丢掉的一些东西,自然就是最后信用金和现金一起付的钱了

首先,如果\(sum<11000\),信用金不可能足以支付最后一笔钱,我们可以大致确定\(tmp=(sum-a[n])/10\)

接着,如果我们通过贪心求得的最大信用金为\(200*k+100\),并且所有\(a_i==2000\),那么我们知道我们不可能有那100信用金,,减掉即可

如果这样就算贪心的证明的话,好像不能让人信服,但我一时半会好像找不到什么讨论的落脚点,不如大佬们在评论区提出,我有时间一定解答

大概代码长这样

	n=read();
	for(int i=1;i<=n;i++){
		a[i]=read()/100;
		sum+=a[i];tot+=(a[i]==10);
	}
	tmp=sum/11;
	if(sum<=110)tmp=sum/10-a[n]/10;
	if(!tot&&tmp%2==1)tmp--;
	cout<<sum*100-tmp*100<<endl;

day1 题目写了接近一天,连小结都支支吾吾写了半天,果然我水平比较差

总的来说这些题目还是比较难的,看上去好像只能暴力的模拟,然而细心思量,你掌握的dp,数学转化和搜索,贪心等等算法,不都应该灵活自如的运用吗

虽然数据结构有的比较难写,而且好久不碰代码导致及其生疏,那又怎么样呢?

加油啊,勇敢的战啊

勇气,是通往一切梦想的大门

posted @ 2020-10-31 21:02  千陌  阅读(82)  评论(0编辑  收藏  举报