Dashboard - Codeforces Round #566 (Div. 2) - Codeforces
A. Filling Shapes
题意:给你一个的表格,你要用小L型的瓷砖把整个表格填满,问你有多少种方法?
知识点:数学,思维
思路:通过一点点观察发现,想要满足宽度是3,一定是俩个L型瓷砖交叉这放,并且对于一个长度为2宽度为3的表格,只有两种放法,如下图
所以只要表格长度为偶数,那么答案一定是2^n/2(有多少个长度为2的,每个有两种情况)
奇数一定不可能填满,因为没有长度为1的图形来填,也不可能凑出来。
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll N = 1e5+5;
const ll mod =1e9+6;
void solve(){
ll n;cin>>n;
if(n&1)cout<<0<<'\n';//奇数没有
else cout<<(1ll<<(n/2))<<'\n';//偶数 2^(n/2)个
}
int main() {
ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);
int _=1;//cin>>_;
while(_--){
solve();
}
return 0;
}
/*
*/
B. Plus from Picture
题意:有一个大小的表格,表格上每一个位置只会出现两种字符,‘+’和‘.’,问是否所有的‘+’连接成了一个大的 + 号(所有‘+’字符都在这个大的 + 号上,大的 + 号四个延伸出去的边长度至少大于等于1,宽度等于1,中间不能是空的)?
知识点:搜索,思维
思路:我们先看每一个点是否可以是大 + 号中间那个点,如果是放入我们的数组里,然后先判断数组的大小,如果大小大于1或者等于0,就是说不满足所有的点都在大的 + 号上或者说 大的 + 号的边的宽度大于1,这些都是NO的,现在只剩下了数组大小等于1,我们搜4个方向上所能走的点打上标记,最后看整个图,如果存在没有打上标记的‘+’,说明不是所有的‘+’构成了大的 + 号,输出NO,否则输出 YES
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll N = 1e5+5;
const ll mod =1e9+6;
char mp[505][505];
ll n,m;
bool vis[505][505];
vector<pair<ll,ll> >vp;
ll dx[5]={0,0,0,1,-1};
ll dy[5]={0,1,-1,0,0};
bool check(ll x,ll y){
for(int i=0;i<5;++i)if(mp[x+dx[i]][y+dy[i]]!='*')return false;
return true;
}
void solve(){
cin>>n>>m;
for(int i=1;i<=n;++i){
for(int j=1;j<=m;++j){
cin>>mp[i][j];
if(mp[i][j]=='.')vis[i][j]=true;//先给'.'打上标记这样好判断一点
}
}
for(int i=2;i<n;++i){
for(int j=2;j<m;++j){
if(check(i,j))vp.push_back({i,j});//如果可以当中间点,放入数组里
}
}
if(vp.size()!=1)cout<<"NO\n";//数组大小不为1,一定不可以
else {
auto w=vp[0];
vis[w.first][w.second]=true;//记得给初始点打标记
for(int i=1;i<5;++i){//四个方向
for(ll j=1;j<505;++j){//所能走的最长长度
ll tx=w.first+dx[i]*j,ty=w.second+dy[i]*j;
if(tx<1||ty<1||tx>n||ty>m||vis[tx][ty])break;
//如果遇到打标记的点,说明遇到'.'了,这个方向就不用看了
vis[tx][ty]=true;
}
}
for(int i=1;i<=n;++i){
for(int j=1;j<=m;++j){
if(!vis[i][j]){//如果存在没有打标记的点,输出NO
cout<<"NO\n";
return ;
}
}
}
cout<<"YES\n";//都成立了,输出YES
}
}
int main() {
ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);
int _=1;//cin>>_;
while(_--){
solve();
}
return 0;
}
/*
*/
C. Beautiful Lyrics
题意:给你n个字符串,现在问你能从中选出的美好的诗句有几条?
美好的诗句满足:
(1)美好的诗句由4个字符串组成,分为左上,左下,右上,右下。
(2)左上和左下的元音字母个数要相同。
(3)右上和右下的元音字母个数要相同,并且右上和右下的最后一个元音字母一样。
(元音字母只包括 a,e,i,o,u)
知识点:贪心,排序
思路:发现条件(3)是包含条件(2)的,所以肯定是条件(3)满足的越多越好,因为多余的可以退化成满足(2),所以现在问题转化成了问有多少个满足条件(3)的字符串对和剩下的有多少个满足条件(2)的字符串对。这个我们可以先按元音字母个数从小到大排序,再按最后一个元音字母的大小从小到大排序,然后按要求找就OK了,具体实现可以看代码。
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll N = 1e5+5;
const ll mod =1e9+6;
struct dd{
ll cnt,last;//元音字母个数,最后一个元音字母
string s;//这个字符串是什么
inline bool operator <(const dd &r)const{
if(cnt==r.cnt)return last<r.last;//再按最后一个元音字母从小到大排序
return cnt<r.cnt;//先按元音字母个数从小到大排序
}
}op[N];
bool vis[N];//打标记用
void solve(){
ll n;cin>>n;
for(int i=1;i<=n;++i){
string s;cin>>s;
ll cnt=0,last=0;
for(int i=0;i<s.length();++i){
if(s[i]=='a'||s[i]=='e'||s[i]=='i'||s[i]=='o'||s[i]=='u'){
cnt++;
last=s[i]-'a';
}
}
op[i]={cnt,last,s};
}
sort(op+1,op+n+1);
vector<pair<string,string> >ansr,ansl;//记录答案,r是满足(3),l是满足(2)
ll pre=-1,precnt=-1,preid=0;//上一个状态的最后一个元音字母,元音字母个数,下标
for(int i=1;i<=n;++i){
if(pre==op[i].last&&precnt==op[i].cnt){//如果满足条件(3)
ansr.push_back({op[i].s,op[preid].s});//先扔进去
vis[i]=true;//这个下标已经用过了,打标记
vis[preid]=true;//这个下标已经用过了,打标记
pre=-1;precnt=-1;preid=0;//这俩都已经用了,说明没有上一个
}
else {//不满足,更新
pre=op[i].last;
precnt=op[i].cnt;
preid=i;
}
}
//满足条件(3)的找完了,找满足条件(2)的
pre=-1;precnt=-1;preid=0;//上一个状态的最后一个元音字母,元音字母个数,下标
for(int i=1;i<=n;++i){
if(vis[i])continue;//用过,说明不能用了
if(precnt==op[i].cnt){//条件(2)只用满足元音字母个数相同
ansl.push_back({op[i].s,op[preid].s});//先扔进去
pre=-1;precnt=-1;preid=0;//这俩都已经用了,说明没有上一个
}
else {//不满足,更新
pre=op[i].last;
precnt=op[i].cnt;
preid=i;
}
}
ll lsize=ansl.size(),rsize=ansr.size();
if(lsize>=rsize){//如果满足条件(2)的比满足条件(3)的多
cout<<rsize<<'\n';
for(int i=0;i<rsize;++i){
cout<<ansl[i].first<<' '<<ansr[i].first<<'\n';
cout<<ansl[i].second<<' '<<ansr[i].second<<'\n';
}
}
else {//如果满足条件(2)的比满足条件(3)的少,因为满足条件(3)可以退化成满足条件(2)的
//个数就是
ll tmp=(rsize-lsize)/2;
for(int i=rsize-1;i>=rsize-tmp;--i){//然后把他们扔过去
ansl.push_back(ansr[i]);
}
cout<<lsize+tmp<<'\n';
for(int i=0;i<lsize+tmp;++i){
cout<<ansl[i].first<<' '<<ansr[i].first<<'\n';
cout<<ansl[i].second<<' '<<ansr[i].second<<'\n';
}
}
}
int main() {
ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);
int _=1;//cin>>_;
while(_--){
solve();
}
return 0;
}
/*
*/
D. Complete Mirror
题意:给你一颗有n个结点的无根树,问你是否能找到一个点作为根满足:
对于任意两点满足
并且
(表示点i到点j之间的边的数量,
表示点i和多少个点直接相连)
找到输出点的编号,找不到输出-1
知识点:DFS,树的重心
思路:如果暴力的想,就是n个点都可能成为根,每个都判一次,复杂度O()肯定是不可以的,所以想尽可能减少判的次数,发现有一些点是没有判的必要的,所以现在就是考虑哪些点可能成为根
先考虑条件,这两个条件要都满足一定是这个点孩子的子树都是同构的,而想要满足这个条件,那么这个点可能在树的重心位置上,所以重心是可能成为根的一个点,然后考虑重心的孩子,如果孩子的子树是一个树型的,那这个子树上的所有点都不可能是根,因为上面所有点都不可能满足条件的。如果孩子的子树一个链型的,那这个子树的叶子是可能成为根的,但其实这里不用全判,如果俩个链长度相同,取其一就行,毕竟是同构的,所以我们要判断的就很少了。
(这道题其实多动手画图就能发现了)
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll N = 1e5+5;
const ll mod =1e9+7;
ll n;
ll siz[N],weight[N];//求重心用的
ll treefocus[2];//树的重心下标
vector<ll>mp[N];//存树的边
void dfs(ll u,ll root){//求树的重心
siz[u]=1;weight[u]=0;
for(auto v:mp[u]){
if(v==root)continue;
dfs(v,u);
siz[u]+=siz[v];
weight[u]=max(weight[u],siz[v]);
}
weight[u]=max(weight[u],n-siz[u]);
if(weight[u]<=n/2){//根据定义判断树的重心
treefocus[treefocus[0]!=0]=u;
}
}
vector<ll>ans;//要判断的数组
ll d[N],ye,Maxdeep;//度数,叶子的下标,链的长度
bool tp,in[N];//是不是链,有没有放过
void DFS(ll u,ll root,ll deep){
ll ge=1;//孩子个数
Maxdeep=max(Maxdeep,deep);
for(auto v:mp[u]){
if(v==root)continue;
DFS(v,u,deep+1);
ge--;
}
if(ge==1)ye=u;//叶子没有孩子
if(ge<0)tp=false;//个数小于0了,说明不是链
}
bool flag;
ll op[N],maxdeep;
void check(ll now,ll root,ll deep){//判断是不是满足条件
maxdeep=max(maxdeep,deep);
if(op[deep]){//同一深度,度数要相等
if(d[now]!=op[deep])flag=false;
}
else op[deep]=d[now];
for(auto v:mp[now]){
if(v==root)continue;
check(v,now,deep+1);
}
}
void solve(){
cin>>n;
for(ll i=1,u,v;i<n;++i){
cin>>u>>v;
mp[u].push_back(v);
mp[v].push_back(u);
d[u]++;d[v]++;
}
dfs(1,-1);//求树的重心
ans.push_back(treefocus[0]);//放入要判断的数组中
for(auto v:mp[ans[0]]){//判断重心的孩子
tp=true;Maxdeep=0;
DFS(v,ans[0],1);
if(tp){
if(in[Maxdeep])continue;//如果这个长度的链已经放入要判断的数组了,就不用放了
in[Maxdeep]=true;
ans.push_back(ye);
}
}
for(auto w:ans){
flag=true;
check(w,-1,1);
for(int i=1;i<=maxdeep;++i)op[i]=0;
if(flag){//满足
cout<<w<<'\n';//输出满足的下标
return ;
}
}
cout<<-1<<'\n';//没有满足的
}
int main() {
ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);
int _=1;//cin>>_;
while(_--){
solve();
}
return 0;
}
/*
*/
E. Product Oriented Recurrence
题意:,已知
的值,求
?
知识点:矩阵快速幂,欧拉定理
思路:发现最后一定等于
,所以我们可以构造一个关于这几项幂的矩阵,然后矩阵快速幂加速,我是通过分别构造
的矩阵和
的矩阵来写的。
其中分别表示答案中
的多少次幂
其中表示答案中
的多少次幂
最后输出就可以了(矩阵里是对phi(1e9+7)取模,因为欧拉定理)
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll N = 1e5+5;
const ll mod =1e9+6;
const ll Mod =1e9+7;
const int maxl=105;
struct Matrix {
ll a[maxl][maxl];
int n,m;
inline Matrix(int n,int m) : n(n) , m(m) {
for(int i=0;i<n;++i){
for(int j=0;j<m;++j)a[i][j]=0;
}
}
inline void init(){
for(int i=0;i<n;++i){
for(int j=0;j<m;++j)a[i][j]=0;
a[i][i]=1;
}
}
inline void Clear(){
for(int i=0;i<n;++i){
for(int j=0;j<m;++j)a[i][j]=0;
}
}
inline Matrix operator * (const Matrix &t){
Matrix io(n,t.m);
for(int i=0;i<n;++i){
for(int j=0;j<t.m;++j){
for(int k=0;k<m;++k){
io.a[i][j]=(io.a[i][j]+a[i][k]*t.a[k][j]%mod+mod)%mod;
}
}
}
return io;
}
inline Matrix operator + (const Matrix &t){
Matrix io(n,m);
for(int i=0;i<n;++i){
for(int j=0;j<m;++j){
io.a[i][j]=(a[i][j]+t.a[i][j]+mod)%mod;
}
}
return io;
}
inline Matrix ksm(ll b){
Matrix ans(this->n,this->m),a=*this;
ans.init();
while(b){
if(b&1)ans=ans*a;
a=a*a;
b>>=1;
}
return ans;
}
};
ll ksm(ll a,ll b){
ll ans=1;
while(b){
if(b&1)ans=ans*a%Mod;
a=a*a%Mod;
b>>=1;
}
return ans;
}
ll f[3],n,c;
void solve(){
cin>>n;
for(int i=2;i>=0;--i)cin>>f[i];//倒着输入对标一下
cin>>c;
Matrix now(3,3);
for(int i=0;i<3;++i)now.a[0][i]=1;//构造f1,f2,f3
now.a[1][0]=now.a[2][1]=1;// 矩阵 矩阵里是对phi(1e9+7)取模
now=now.ksm(n-3);
ll ans=1;
for(int i=0;i<3;++i){
f[i]=ksm(f[i],now.a[0][i]);
ans=ans*f[i]%Mod;//更新答案
}
Matrix cl(5,5);
for(int i=0;i<3;++i)cl.a[0][i]=1; //构造c
cl.a[0][3]=2;cl.a[0][4]=mod-4; //的
cl.a[1][0]=cl.a[2][1]=cl.a[3][3]=cl.a[3][4]=cl.a[4][4]=1;//矩阵
cl=cl.ksm(n-3);
ll w=(cl.a[0][3]*3ll%mod+cl.a[0][4])%mod;//对phi(1e9+7)取模,因为是次幂
cout<<ans*ksm(c,w)%Mod<<'\n';//输出答案
}
int main() {
ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);
int _=1;//cin>>_;
while(_--){
solve();
}
return 0;
}
/*
*/
F. Maximum Sine
题意:给你四个整数,问你函数
在
中找到最小的x让y最大?
知识点:类欧几里德,扩展欧几里得(需要一定的数论基础
思路:根据一点点高中知识,可以知道在
时候取到最大值,所以我们要
也就是
等价于
尽可能趋近0或者2q,
我们可以考虑二分对q的偏差,
我们首先要知道一个定理:等价于
这个也很好验证:如果,后面那个等于-1-(-1)=0
如果,后面那个等于0-(-1)=1
如果,后面那个等于0-0=0
如果存在x满足,那么一定满足
后面这个其实就是一个类欧几里德的东西,(类欧几里德就不展开讲了,这里就用了个板子)然后我们就能二分出来对q的最小偏差d
也就是我们知道和
现在我们要还原x的值,发现这俩个就是个同余方程,我们用扩展欧几里得解一下就好了
还原出来x记得要把x再更新到这个区间里,取两者的最小值就行了
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll N = 1e5+5;
const ll mod =1e9+7;
ll calc(ll a,ll b,ll c,ll n){//类欧几里德
if(n<0)return 0;
if(n==0)return b/c;
if(a>=c||b>=c)return n*(n+1)/2*(a/c)+(n+1)*(b/c)+calc(a%c,b%c,c,n);
ll m=(a*n+b)/c;
return m*n-calc(c,c-b-1,a,m-1);
}
ll a,b,p,q,Q,P,x,y,g;
bool check(ll l,ll r){
return calc(P,Q-l,Q,b)-calc(P,Q-l,Q,a-1)-calc(P,Q-r-1,Q,b)+calc(P,Q-r-1,Q,a-1);
//这里的Q-l和Q-r-1和l,r-1是一样的,只不过防止了负数
}
ll exgcd(ll a,ll b,ll &x,ll &y){//扩展欧几里得
if(!b){x=1;y=0;return a;}
ll g=exgcd(b,a%b,x,y);
ll tmp=x;x=y;
y=tmp-(a/b)*y;
return g;
}
void solve(){
cin>>a>>b>>p>>q;
Q=q<<1;P=p<<1;
ll l=0,r=q,mid;
while(l<=r){//二分偏差
mid=l+r>>1;
ll L=q-mid,R=q+mid;//偏差后的两个值
if(check(L,R))r=mid-1;
else l=mid+1;
}
g=exgcd(P,Q,x,y);
ll ans=1e18;//默认最大值
if((q-l)%g==0){
ll t=Q/g,now=(x*((q-l)/g)%t+t)%t;
while(now>=a)now-=t;//限制成最小
while(now<a)now+=t;//限制
ans=min(ans,now);
}
if((q+l)%g==0){
ll t=Q/g,now=(x*((q+l)/g)%t+t)%t;
while(now>=a)now-=t;//限制成最小
while(now<a)now+=t;//限制
ans=min(ans,now);
}
cout<<ans<<'\n';
}
int main() {
ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);
int _=1;cin>>_;
while(_--){
solve();
}
return 0;
}
/*
*/