补题链接:https://pintia.cn/market/item/1534086632285245440
B FFT
诈骗题:单看求解的东西很复杂,其实整个集合就是所有的排列,答案即为n!
const int N = 1e7+10;
int ans[N];
int fac[N],inv[N];
void laoya()
{
int n;
cin>>n;
fac[0]=inv[0]=1;
for(int i=1;i<=n;i++)
{
fac[i]=fac[i-1]*i%mod;
}
cout<<fac[n]<<endl;
}
D 剪纸
不会思路,不过很容易打表,打完发现除了2是1 1外其他数是小于等于n的前两个
下面是题解
void laoya()
{
int n;
cin>>n;
if(n==2)
{
cout<<1<<" "<<1<<endl;
return ;
}
int f1=1,f2=1;
while(1)
{
int f3=f1+f2;
if(f3>n)break;
f1=f2;
f2=f3;
}
cout<<f1<<" "<<f2<<endl;
}
E 黑白大陆
思路:把0和1的联通块分别缩点,缩完点后在0和1的的联通块之间加边,建完图后,我们可以发现,如果我们当前在u,有u->v1,u->v2的边,如果我们对u连通块使用一次操作,那么就会走到v1,v2点,即三个点连通了,最后的颜色为v1,v2的颜色,于是我们可以发现一个性质,从某个点出发后每次对自己染色,就相当于走一条边,那么我们bfs一遍求最短路,最短路里面最长的那个点就是我们从这个点出发把整个联通块染成这个颜色的步数,要注意的是如果终点颜色是1的话,我们要加上一步把整个联通块变成0
代码
const int N = 51;
int n,m;
int a[N][N];
int col[N*N];//联通块的颜色
vector<int> v[N*N];
int cnt;
int id[N*N],d[N*N];
bool vis[N*N];
int dx[4]={0,0,1,-1},dy[4]={1,-1,0,0};
void dfs(int i,int j,int belong)
{
int pos=(i-1)*m+j;
if(vis[pos])return ;
vis[pos]=1;
id[pos]=belong;
for(int k=0;k<4;k++)
{
int x=i+dx[k],y=j+dy[k];
if(x<=0 || x>n || y<=0 || y>m)continue;
if(a[i][j]==a[x][y])dfs(x,y,belong);
}
}
int bfs(int u) //从u号点出发
{
memset(d,0x3f,sizeof d);
memset(vis,0,sizeof vis);
queue<int> q;
d[u]=0;
vis[u]=1;
q.push(u);
while(q.size())
{
int u=q.front();
q.pop();
for(auto to:v[u])
{
if(!vis[to])
{
d[to]=d[u]+1;
vis[to]=1;
q.push(to);
}
}
}
int ma=0;
for(int i=1;i<=cnt;i++)
{
if(col[i]==1)d[i]++; //如果终点是1那么要多用一次
ma=max(d[i],ma);
}
return ma;
}
void laoya()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
cin>>a[i][j];
int pos=(i-1)*m+j;
}
}
//缩点
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
int pos=(i-1)*m+j;
if(!vis[pos])
{
cnt++;
dfs(i,j,cnt);
col[cnt]=a[i][j];
}
}
}
//加边
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
int b=(i-1)*m+j;
for(int k=0;k<4;k++)
{
int x=i+dx[k],y=j+dy[k];
if(x<=0 || x>n || y<=0 || y>m)continue;
int c=(x-1)*m+y;
if(a[i][j]!=a[x][y])
{
v[id[b]].push_back(id[c]);
}
}
}
}
for(int i=1;i<=cnt;i++) //删掉重边
{
sort(v[i].begin(),v[i].end());
v[i].erase(unique(v[i].begin(),v[i].end()),v[i].end());
}
//对每一个点跑最短路
int ans=1e9;
for(int i=1;i<=cnt;i++)ans=min(ans,bfs(i));
cout<<ans<<endl;
}
F 望舒客栈的每日委托
思路:模拟,用5个set分别存每一张桌子的编号即可,枚举时间,用自定义的deque或set实现弹出队头和插入队尾操作即可
代码
const int N = 1e7+10;
int n;
int sum[N]; //第i张桌子有sum[i]个人
int belong[N]; //id这个人属于哪一张桌子
set<int> num[6]; //还剩i个人的桌子的编号
struct node
{
int id,x,a,d,t;
bool operator<(const node &t)
{
return a<t.a;
};
}q[N];
class Mycompare{
public:
bool operator()(int a, int b){//重载运算符
return q[a].d < q[b].d;//降序排列
}
};
void laoya()
{
cin>>n;
for(int i=1;i<=n;i++)
{
int x,a,d,t;
cin>>x>>a>>d>>t;
q[i]={i,x,a,d,t};
}
sort(q+1,q+1+n); //按照到来时间排序
int tot=0; //总共tot张桌子
int idx=1;
set<int,Mycompare> st; //在队列中的人,按照离开时间从小到大排序
for(int time=1;time<=2*n;time++)
{
//出队
while(st.size() && q[*st.begin()].d<time)
{
//人的编号
int pos=*st.begin();
//belong[id]是桌子编号
num[sum[belong[pos]]].erase(belong[pos]);
sum[belong[pos]]+=q[pos].x;
num[sum[belong[pos]]].insert(belong[pos]);
st.erase(pos);
}
while(idx<=n && q[idx].a==time) //入队
{
st.insert(idx);
if(!q[idx].t) //社恐
{
int x=q[idx].x;
if(num[4].size()) //剩下4个人的桌子
{
int t=*(num[4].begin());
num[4].erase(t);
belong[idx]=t;
sum[t]-=x;
num[sum[t]].insert(t);
}
else //再开一张新桌子
{
tot++;
belong[idx]=tot;
sum[tot]=4-x;
num[sum[tot]].insert(tot);
}
}
else //社牛
{
int pos=1e9;
int x=q[idx].x;
for(int i=x;i<=4;i++) //找到第一张可以坐的桌子
{
if(num[i].size())pos=min(pos,*(num[i].begin()));
}
if(pos==1e9) //再开一张新桌子
{
tot++;
belong[idx]=tot;
sum[tot]=4-x;
num[sum[tot]].insert(tot);
}
else
{
num[sum[pos]].erase(pos);
belong[idx]=pos;
sum[pos]-=x;
num[sum[pos]].insert(pos);
}
}
idx++;
}
}
cout<<tot<<endl;
}
H 梅花易数
思路:按照题意模拟即可
vector<string> ans;
void get(int x)
{
string s[3]={"","",""};
if(x==1)
{
s[0]+="---";
s[1]+="---";
s[2]+="---";
}
else if(x==2)
{
s[0]+="- -";
s[1]+="---";
s[2]+="---";
}
else if(x==3)
{
s[0]+="---";
s[1]+="- -";
s[2]+="---";
}
else if(x==4)
{
s[0]+="- -";
s[1]+="- -";
s[2]+="---";
}
else if(x==5)
{
s[0]+="---";
s[1]+="---";
s[2]+="- -";
}
else if(x==6)
{
s[0]+="- -";
s[1]+="---";
s[2]+="- -";
}
else if(x==7)
{
s[0]+="---";
s[1]+="- -";
s[2]+="- -";
}
else
{
s[0]+="- -";
s[1]+="- -";
s[2]+="- -";
}
for(int i=0;i<3;i++)
{
ans.push_back(s[i]);
}
}
void laoya()
{
map<string,int> mp;
mp["Zi"]=1;
mp["Chou"]=2;
mp["Yin"]=3;
mp["Mao"]=4;
mp["Chen"]=5;
mp["Si"]=6;
mp["Wu"]=7;
mp["Wei"]=8;
mp["Shen"]=9;
mp["You"]=10;
mp["Xu"]=11;
mp["Hai"]=12;
string yy,hh;
int y,m,d,h;
cin>>yy>>m>>d>>hh;
y=mp[yy];
h=mp[hh];
get((y+m+d)%8);
get((y+m+d+h)%8);
for(int i=0;i<6;i++)cout<<ans[i]<<endl;
cout<<endl;
int idx=(y+m+d+h)%6;
if(idx==0)idx=0;
else idx=6-idx;
if(ans[idx][1]==' ')ans[idx][1]='-';
else ans[idx][1]=' ';
for(int i=0;i<6;i++)cout<<ans[i]<<endl;
}
J 新英雄
思路:有线性写法,不过我简单二分了一下,注意坑点是输入要排序(找了好久没发现错哪了)
假设我们总共走了k步,首先我们得每一步走完后的总法力值时刻大于等于0,其次我们可以选择(k+1)/2个选择踏,k/2个选择踩。
具体做法:首先如果第一个小兵是敌人,那么我肯定是走不到终点的直接return。第一个小兵是朋友,那么我们只要二分向这个小兵借了多少步,然后线性扫一遍如果前缀时刻大于等于0那么就true
代码
const int N = 2e5+10;
int op[N],l[N],r[N];
int n,m;
int ans=1e18;
struct node
{
int x,y,z;
}q[N];
bool cmp(node a,node b)
{
return a.y<b.y;
}
bool check(int jie)
{
int tmpl=l[1];
int sum=jie;
if(jie%2==1)
{
l[1]++;
}
bool flag=1;
for(int i=l[1]<=r[1]?1:2;i<=m;i++)
{
int len=r[i]-l[i]+1;
if(op[i]==1)sum+=len;
else sum-=len;
if(sum<0)
{
flag=0;
break;
}
}
l[1]=tmpl;
if(flag)
{
int tot=jie;
if(jie%2==1)tot+=n-1;
else tot+=n;
ans=min(ans,(tot+1)/2*3+tot/2);
return 1;
}
return 0;
}
void laoya()
{
cin>>n>>m;
for(int i=1;i<=m;i++)
{
cin>>q[i].x>>q[i].y>>q[i].z;
}
sort(q+1,q+1+m,cmp);
for(int i=1;i<=m;i++)
{
op[i]=q[i].x;
l[i]=q[i].y;
r[i]=q[i].z;
}
if(op[1]==2) //根本走不了
{
cout<<"0/21/0"<<endl;
return ;
}
int l=0,r=n+2;
while(l<r)
{
int mid=l+r>>1;
if(check(mid))r=mid;
else l=mid+1;
}
cout<<ans<<endl;
}
K 斐波那契
这题解已经很明确了,这个将可达性矩阵变成斐波那契矩阵非常妙。由于矩阵乘法的性质,我们把本来权值为i的边变成斐波那契矩阵的第i项个项,可以画两个矩阵就能发现这样算出来的答案恰好就是矩阵乘的答案,还有一点是这个题卡常数,取模运算改成add函数后非常勉强的过了
const int mod = 998244353;
int n,m,k;
vv a(2,vi(2));
inline void add(ll &x,ll y)
{
x=x+y;
if(x>=mod)x-=mod;
}
inline vv mul(vv a,vv b) //斐波那契矩阵乘
{
vv c(2,vi(2));
for(int i=0;i<2;i++)
{
for(int j=0;j<2;j++)
{
for(int k=0;k<2;k++)
{
int tmp=a[i][k]*b[k][j]%mod;
add(c[i][j],tmp);
}
}
}
return c;
}
inline vv f(vv a,int b) //斐波那契第x项矩阵
{
vv ans(2,vi(2));
ans[0][0]=ans[1][1]=1;
while(b)
{
if(b&1)ans=mul(ans,a);
a=mul(a,a);
b>>=1;
}
return ans;
}
//可达性矩阵乘法
inline vv operator*(vv a,vv b)
{
vv c(2*n+1,vi(2*n+1));
for(int i=1;i<=2*n;i++)
{
for(int j=1;j<=2*n;j++)
{
for(int k=1;k<=2*n;k++)
{
int tmp=a[i][k]*b[k][j]%mod;
add(c[i][j],tmp);
}
}
}
return c;
}
inline vv qpow(vv a,int b)
{
vv ans(2*n+1,vi(2*n+1));
for(int i=1;i<=2*n;i++)ans[i][i]=1;
while(b)
{
if(b&1)ans=ans*a;
a=a*a;
b>>=1;
}
return ans;
}
void laoya()
{
a={
{0,1},
{1,1}
};
cin>>n>>m>>k;
vv A(2*n+1,vi(2*n+1)); //可达性矩阵
for(int i=1;i<=m;i++)
{
int u,v,w;
cin>>u>>v>>w;
vv g=f(a,w); //边权转化为矩阵
int x0,y0,x1,y1;
x1=u*2;
y1=v*2;
x0=x1-1;
y0=y1-1;
//叠加到可达性矩阵上面
add(A[x0][y0],g[0][0]);
add(A[x0][y1],g[0][1]);
add(A[x1][y0],g[1][0]);
add(A[x1][y1],g[1][1]);
}
//求A的k次方即可
A=qpow(A,k);
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
cout<<A[i*2-1][j*2];
if(j!=n)cout<<" ";
}
cout<<'\n';
}
}
L 起航者
思路:我是换根dp做的,看了题解好像有点类似。
数组:down[i]表示以1为根 i 往下走的答案,
dp[i][0]表示从i出发走最大的那个点的答案
dp[i][1]表示从i出发走第二大的那个点的答案
第一步:第一步先求出每个点连着的最大的两个点ma[i][0],ma[i][1],再跑一遍以1为根的dfs1(即从1号点出发),求出每个节点只向下走的答案down[i],此时down[1]其实也就是从1号点出发的答案了。
第二步:有了down数组,我们就可以求出dp[1][0],dp[1][1];
for(int i=1;i<=n;i++)
{
if(w[i]==ma[1][0])dp[1][0]=w[1]+down[i];
else if(w[i]==ma[1][1])dp[1][1]=w[1]+down[i];
}
第三步:我们继续dfs一遍,进行换根dp,假设当前dfs到u这个节点,他的父亲fa的dp[fa][0],dp[fa][1]已经求出来了(因为1号已经求出来了,往下能不断求出来)那么我们可以进行讨论
分类讨论如下
一.往父亲走
1.如果父亲是我最大的点
(1).我是父亲最大的点,那么父亲只能走第二大的点,
dp[u][0]=w[u]+dp[fa][1];
(2).我不是父亲最大的点,那么父亲就走最大的点
dp[u][0]=w[u]+dp[fa][0];
2.父亲是我第二大的点
(1).我是父亲最大的点,那么父亲只能走第二大的点;
dp[u][1]=w[u]+dp[fa][1];
(2).我不是父亲最大的点,那么父亲就走最大的点
dp[u][1]=w[u]+dp[fa[[0];
二.往儿子走(儿子为to)
1.如果儿子是最大的点
dp[u][0]=w[u]+down[to];
2.如果儿子是第二大的点;
dp[u][1]=w[u]+down[to];
注意递归时是先把当前的dp[u][0]和dp[u][1]算出来后再往下dfs
const int N = 1e6+10;
int n;
vector<int> v[N];
int w[N];
int ma[N][2];
int dp[N][2];
int down[N];
void getma(int x,int y)
{
if(w[y]>ma[x][0])
{
ma[x][1]=ma[x][0];
ma[x][0]=w[y];
}
else if(w[y]>ma[x][1])
{
ma[x][1]=w[y];
}
}
void dfs1(int u,int fa)
{
for(auto to:v[u])
{
if(to==fa)continue;
dfs1(to,u);
}
int ma=0,idx=0;
for(auto to:v[u])
{
if(to==fa)continue;
if(w[to]>ma)
{
ma=w[to];
idx=to;
}
}
down[u]=w[u]+down[idx];
}
void dfs(int u,int fa)
{
for(auto to:v[u])
{
if(to==fa && u!=1)
{
if(w[fa]==ma[u][0]) //父亲是我最大的点
{
if(w[u]==ma[fa][0]) //我是父亲最大的点
{
dp[u][0]=w[u]+dp[fa][1];
}
else //我不是父亲最大的点
{
// if(u==5)cout<<u<<" "<<fa<<" "<<dp[fa][0]<<endl;
dp[u][0]=w[u]+dp[fa][0];
}
}
if(w[fa]==ma[u][1]) //父亲是我第二大的点
{
if(w[u]==ma[fa][0]) //我是父亲最大的点
{
dp[u][1]=w[u]+dp[fa][1];
}
else //我不是父亲最大的点
{
dp[u][1]=w[u]+dp[fa][0];
}
}
continue;
}
else
{
if(w[to]==ma[u][0])
{
dp[u][0]=w[u]+down[to];
}
else if(w[to]==ma[u][1])
{
dp[u][1]=w[u]+down[to];
}
}
}
for(auto to:v[u])
{
if(to==fa)continue;
dfs(to,u);
}
}
void laoya()
{
int n;
cin>>n;
for(int i=2;i<=n;i++)
{
int fa;
cin>>fa;
v[i].push_back(fa);
v[fa].push_back(i);
}
for(int i=1;i<=n;i++)
{
cin>>w[i];
ma[i][0]=ma[i][1]=-1;
dp[i][0]=dp[i][1]=w[i];
}
for(int i=1;i<=n;i++)
{
for(auto x:v[i])
{
getma(i,x);
}
}
dfs1(1,1); //求down数组
//求从1号点出发的最大的两条路
for(int i=1;i<=n;i++)
{
if(w[i]==ma[1][0])dp[1][0]=w[1]+down[i];
else if(w[i]==ma[1][1])dp[1][1]=w[1]+down[i];
}
dfs(1,1);
int ans=0;
for(int i=1;i<=n;i++)
{
ans=max(ans,dp[i][0]);
}
for(int i=1;i<=n;i++)
{
if(dp[i][0]==ans)
{
cout<<i<<endl;
cout<<ans<<endl;
return ;
}
}
}