Codeforces Educational 181(ABCD)
前言
刚刚搞完暑假实践回家,然后又得马不停蹄地赶数学建模的论文,现在总算是可以回归算法竞赛的正轨了。
发几句牢骚,这个23国赛的C题看着简单,实际上做起来要注意的坑还挺多的……最困难的还是学的模型和算法不够多,讲解视频里的启发式算法都没学过,看来八月份还有得学了……
原本计划着这个暑假看完左老师的拓展课,然后学点工程cpp和计算机系统,现在看来是有点困难了,走一步看一步吧,先把rating打上去再说……
一、A. Difficult Contest
#include <bits/stdc++.h>
using namespace std;typedef long long ll;
typedef pair<int,int> pii;
typedef pair<ll,ll>pll;void solve()
{string s;cin>>s;map<char,int>mp;for(int i=0;i<s.length();i++){mp[s[i]]++;}string ans;while(mp['T']>0){ans+="T";mp['T']--;}for(auto iter=mp.begin();iter!=mp.end();iter++){while(iter->second>0){ans+=iter->first;iter->second--;}}cout<<ans<<endl;
}int main()
{ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);int t=1;cin>>t;while(t--){solve(); }return 0;
}
这个题上来大意了WA了一次,但仔细想想其实挺简单的。
观察违规子串的规律,可以发现都是“T”字符在“F”和“N”字符的后面。所以在构造的时候只需要先统计一次各个字符的词频,然后先把“T”字符全输出到字符串的前面,之后再输出其他字符就一定不会违规。
二、B. Left and Down
#include <bits/stdc++.h>
using namespace std;typedef long long ll;
typedef pair<int,int> pii;
typedef pair<ll,ll>pll;ll gcd(ll a,ll b)
{return b==0?a:gcd(b,a%b);
}void solve()
{ll a,b,k;cin>>a>>b>>k;ll g=gcd(a,b);if(k>=(a/g)&&k>=(b/g)){cout<<1<<endl;}else{cout<<2<<endl;}
}int main()
{ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);int t=1;cin>>t;while(t--){solve(); }return 0;
}
一道b题毁了我的上分梦T^T
这道题真就是脑电波没对上,其实贪心一点就能想到,根本不需要什么花里胡哨的走法。不管在什么位置,也不管k的大小是多少,只要选择(0,1)和(1,0)这两种走法就一定可以走到原点,所以代价最大就是2。赛时真让这一点卡死了……
之后就可以考虑用一个代价走到原点的情况,那么对于选择的数对(dx,dy),假设初始位置在(x,y),那么就有dx乘以某个数字t等于x,dy乘以这个数字t等于y。所以就能发现这个数字t就是两个数的最大公约数,那就是检验一下dx和dy是否都在k的范围内。如果在的话就能只消耗一个代价走到,否则就是用两个代价才能走到。
这个题最草的真是那个最多只消耗两次代价的走法没想到,赛时一直在想消耗代价最小的走法,属于是自己给自己绕懵了。
三、C. Count Good Numbers
#include <bits/stdc++.h>
using namespace std;typedef long long ll;
typedef pair<int,int> pii;
typedef pair<ll,ll>pll;//容斥原理
//至少两位数 -> 整体个数减去存在一位质数的个数 -> 2,3,5,7ll count(ll x)
{//不好的数ll ans=0;//大范围的个数ans+=x/2;ans+=x/3;ans+=x/5;ans+=x/7;//重合两次的个数ans-=x/(2*3);ans-=x/(2*5);ans-=x/(2*7);ans-=x/(3*5);ans-=x/(3*7);ans-=x/(5*7);//重合三次的个数ans+=x/(2*3*5);ans+=x/(2*3*7);ans+=x/(2*5*7);ans+=x/(3*5*7);//重合四次的个数ans-=x/(2*3*5*7);//好数 -> 整体减去不好的return x-ans;
}void solve()
{ll l,r;cin>>l>>r;cout<<count(r)-count(l-1)<<endl;
}int main()
{ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);int t=1;cin>>t;while(t--){solve(); }return 0;
}
这个正难则反的思路真没想到……还是太菜了……
首先肯定可以想到,要求l到r内的个数,那就用0~r上的个数减去0~l-1上的个数,那么之后的思路就是求0~x上的个数。又因为要求至少两位数,正着求肯定不好求,那么就考虑转化一下,求存在一位质因子的这些“不好”的数的个数,然后用总数减去这个数即可。
所以可以发现,因为一位的质数只有2,3,5,7,所以那就是一个简单的容斥原理。“不好”的数的个数就是先都加上存在这四个数为质因子的数的个数,然后再减去两两一组的个数,加上三个一组的个数,最后减去四个都有的个数即可。存在某个数为质因子的数的个数就是这个数x直接除以质因子,最后再用x减去不好的数的个数就是符合条件的数。
四、D. Segments Covering
#include <bits/stdc++.h>
using namespace std;typedef long long ll;
typedef pair<int,int> pii;
typedef pair<ll,ll>pll;const int MOD=998244353;//定义dp[i]:0~i-1范围上的格子均被覆盖一次
//所以每次找[i,r]的线段覆盖,转移到dp[r+1]上,乘以这个线段存在的概率
//此外,还要保证中间的线段都不存在
//即右端点在这个线段区间内的所有线段
//考虑用一个前缀积维护不存在的概率//乘法快速幂
ll power(ll x,int n)
{ll ans=1;while(n>0){if((n&1)!=0){ans=(ans*x)%MOD;}x=(x*x)%MOD;n>>=1;}return ans;
}//逆元
ll inv(ll x)
{return power(x,MOD-2);
}void solve()
{int n,m;cin>>n>>m;vector<array<int,4>>a(n);for(int i=0;i<n;i++){cin>>a[i][0]>>a[i][1]>>a[i][2]>>a[i][3];}//每个位置上线段不存在的概率vector<ll>nex(m+1,1);for(int i=0;i<n;i++){int l=a[i][0];int r=a[i][1];int p=a[i][2];int q=a[i][3];nex[r]=(nex[r]*(q-p))%MOD;nex[r]=(nex[r]*inv(q))%MOD;}//前缀积vector<ll>pre(m+1,1);for(int i=1;i<=m;i++){pre[i]=(pre[i-1]*nex[i])%MOD;}//按左边界排序sort(a.begin(),a.end());vector<ll>dp(m+2);dp[1]=1;for(int i=0;i<n;i++){int l=a[i][0];int r=a[i][1];int p=a[i][2];int q=a[i][3];//有效的状态if(dp[l]!=0){//存在的概率ll ans=dp[l]*p%MOD;ans=ans*inv(q)%MOD;//从前缀积里把这个线段不存在的概率除掉ans=ans*q%MOD;ans=ans*inv(q-p)%MOD;ans=ans*pre[r]%MOD;ans=ans*inv(pre[l-1])%MOD;dp[r+1]=(dp[r+1]+ans)%MOD;}}cout<<dp[m+1]<<endl;
}int main()
{ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);int t=1;//cin>>t;while(t--){solve(); }return 0;
}
第一次见这种概率题……
这个题的思路就是讨论每个线段存在和不存在两种情况,那就可以考虑用dp的思想。所以整体过程就是定义dp[i]为0~i-1范围上的格子都正好被一个线段覆盖,所以对于当前位置,就必须要让i~r的线段存在,这样dp[i]就能转移到dp[r+1]。又因为此时需要中间的格子都不能再被别的线段覆盖,那么就需要右边界在这个存在的线段的范围内的所有线段不存在。那么就可以考虑使用前缀和的思想,那就是先计算出nex[r]表示r位置上没有线段覆盖的概率,然后构建前缀积数组pre,所以对于某个线段内不存在其他区间的概率就是这个线段右边界的pre[r]再除以左边界pre[l-1]。
因为涉及除法同余,所以要设置inv函数求q的逆元,之后除的时候乘以inv即可。所以代码就是在构建nex数组时考察所有线段,每次乘以1-p/q,即不存在的概率。之后先按左边界对所有线段排序,方便后续依次处理。接着先初始化dp[1]的概率为1,然后同样考察所有线段。如果dp[l]不等于0,即这个状态有可能达到,那么就先乘以这个线段存在的概率。然后,因为pre数组里计算了一遍当前线段不存在的概率,所以要先把这个概率除掉,再乘以pre[r]/pre[l-1]的概率,最后返回dp[m+1]表示全部线段都正好被一个线段覆盖的概率即可。
总结
edu强度还是有点太高了,总之先开始刷cf上的题吧,尤其是数学和图论这俩困难户。