good problems-4
1.D - Index Trio
设M为最大值,可以证明三元组不会超过MlogM个
d为最大正整数,且\(2^d<=M\),所以三元组不超过\(MlogM\)
暴力枚举
点击查看代码
#include<bits/stdc++.h>
#define ll long long
#define pa pair<ll,int>
using namespace std;
const int maxn=2e5+101;
const int MOD=998244353;
const int inf=2147483647;
const double pi=acos(-1);
const double eps=1e-12;
int read(){
int x=0,f=1;char ch=getchar();
for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1;
for(;isdigit(ch);ch=getchar())x=x*10+ch-'0';
return x*f;
}
int n,a[maxn];
int main() {
n=read();
map<int,ll>mm;int mxxx=0;
for(int i=1;i<=n;i++){
a[i]=read();
mm[a[i]]++;
}
ll ans=0;
for(int i=1;i<=maxn;i++){
for(int j=1;j*i<=maxn;j++)ans+=mm[i]*mm[j]*mm[j*i];
}
cout<<ans;
return 0;
}
2.E - RLE
大致题意就是
有多少个长度为N的字符串,通过操作使得操作完的字符串长度小于原来的字符串
操作就是把连续相同字符变为字符和数字(数字为相同连续的个数):
aaaaabbbccc变为a5b3c3
设dp[i][j]为前i个字符压缩为j个字符的方案数
不难写出转移方程
\(dp[i][j]=\sum_{k=0}^{i-1}dp[k][j-getnum(i-k)-1]*(k==0?26:25)\)
getnum(x)=x的位数,j-getnum(i-k)-1的-1是为字符位
但这是\(O(n^3)\)会超时
尝试用前缀和优化
k是连续的,getnum(i-k)在特定范围是相同的
所以j-getnum(i-k)-1在某些范围是连续的
i-k为1位数,范围为[i-9,i-1]
i-k为2位数,范围为[i-99,i-10]
i-k为3位数,范围为[i-999,i-100]
i-k为4位数,范围为[1,i-1000]
设g[j][i]为压缩为长度为j的字符串,前i个位置的前缀和
g[j][i]=dp[1][j]+dp[2][j]+····+dp[i][j]
注意边界问题,具体见代码
点击查看代码
#include<bits/stdc++.h>
#define ll long long
#define pa pair<ll,int>
using namespace std;
const int maxn=3000+101;
const int inf=2147483647;
const double pi=acos(-1);
const double eps=1e-12;
int read(){
int x=0,f=1;char ch=getchar();
for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1;
for(;isdigit(ch);ch=getchar())x=x*10+ch-'0';
return x*f;
}
int n,MOD;
ll dp[maxn][maxn],g[maxn][maxn];
int getnum(int x){
int ans=0;
while(x){
ans++;
x/=10;
}
return ans;
}
int main() {
n=read();MOD=read();
if(n<=2){
puts("0");
return 0;
}
dp[0][0]=1;
for(int i=1;i<=n;i++){
for(int j=0;j<=2*i && j<n;j++){
{
int t=j-getnum(i)-1;
if(t>=0)(dp[i][j]+=26*dp[0][t]%MOD)%=MOD;
}
{
int x=max(1,i-9),y=i-1;
int t=j-1-1;
if(t>=0 && y>=1)(dp[i][j]+=25*(g[t][y]-g[t][x-1])%MOD)%=MOD;
}
{
int x=max(1,i-99),y=i-10;
int t=j-2-1;
if(t>=0 && y>=1)(dp[i][j]+=25*(g[t][y]-g[t][x-1])%MOD)%=MOD;
}
{
int x=max(1,i-999),y=i-100;
int t=j-3-1;
if(t>=0 && y>=1)(dp[i][j]+=25*(g[t][y]-g[t][x-1])%MOD)%=MOD;
}
{
int x=1,y=i-1000;
int t=j-4-1;
if(t>=0 && y>=1)(dp[i][j]+=25*(g[t][y]-g[t][x-1])%MOD)%=MOD;
}
dp[i][j]%=MOD;
g[j][i]=(g[j][i-1]+dp[i][j])%MOD;
}
}
ll ans=0;
for(int i=0;i<n;i++)ans+=dp[n][i];
printf("%lld\n",(ans%MOD+MOD)%MOD);
return 0;
}
3.D. Very Suspicious
大致题意是在一个蜂巢形状的图中,添加直线,问构造n个等边三角形需要多少个直线,(三角形必须从内部全部为空,换句话说,任何直线或六边形边缘都不能穿过任何三角形)。
有很多做法,说一个特别巧妙地
考虑2条线相交,必然会产生2个60°角,与六边形边界形成2个等边三角形
那么线共有三个方向,设每个方向分别有x,y,z条线
那么总共等边三角形有2(xy+xz+yz)
求x+y+z最小,使得\(n\leq 2*(x*y+x*z+y*z)\)
显然当x=y=z相等时候,x+y+z尽可能小,且2(xy+xz+yz)尽可能大
二分一下x+y+z的值,再判断就可以了
点击查看代码
#include<functional>
#include<algorithm>
#include<iostream>
#include<cstdlib>
#include<cstring>
#include<complex>
#include<string>
#include<cstdio>
#include<vector>
#include<cmath>
#include<queue>
#include<deque>
#include<stack>
#include<map>
#include<set>
#define ll long long
#define pa pair<ll,int>
using namespace std;
const int maxn=2e5+101;
const int MOD=1e9+7;
const int inf=2147483647;
const double pi=acos(-1);
const double eps=1e-12;
int read(){
int x=0,f=1;char ch=getchar();
for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1;
for(;isdigit(ch);ch=getchar())x=x*10+ch-'0';
return x*f;
}
int t;
bool check(ll v,ll n){
ll x=v/3,y=(v+1)/3,z=(v+2)/3;
/*
等价于
int x=v/3,y=x,z=x;
if(v%3==1)z++;
else if(v%3==2)y++,z++;
*/
return 2*(x*y+z*y+z*x)>=n;
}
int main(){
t=read();
while(t--){
int n=read();
int l=0,r=1E9,ans=0;
while(r>=l){
int mid=(l+r)>>1;
if(check(mid,n))r=mid-1,ans=mid;
else l=mid+1;
}
printf("%d\n",ans);
}
return 0;
}
4.E. Hemose on the Tree
给一颗树,你构造边权和点权,并且还自己选定一个根结点。
树的结点个数为\(2^p\),边权和点权范围为\([1,2^{p+1}-1]\),且每个点权和边权都不同
每一条根结点到某点的最短路径的大小为经过的所有边权和点权的异或和
现在要求构造的树,使得根节点到所有节点距离的最大值最小
点击查看代码
#define ll long long
#define pa pair<ll,int>
using namespace std;
const int maxn=3e5+101;
const int MOD=998244353;
const int inf=2147483647;
const double pi=acos(-1);
const double eps=1e-12;
int read(){
int x=0,f=1;char ch=getchar();
for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1;
for(;isdigit(ch);ch=getchar())x=x*10+ch-'0';
return x*f;
}
int n;
int tot,head[maxn],nx[maxn],to[maxn],id[maxn],pos[maxn];
void add(int x,int y,int pos){to[++tot]=y;nx[tot]=head[x];head[x]=tot;id[tot]=pos;}
int a[maxn],cnt;
void dfs(int x,int fa,int now){
for(int i=head[x];i;i=nx[i]){
int v=to[i];if(v==fa)continue;
int x1=++cnt,x2=x1+n;
if(now==n){pos[id[i]]=x2;a[to[i]]=x1;}
else pos[id[i]]=x1,a[to[i]]=x2;
dfs(v,x,now^x1^x2);
}
return ;
}
int main(){
int t=read();
while(t--){
int p=read();n=(1<<p);
for(int i=1;i<n;i++){
int x=read(),y=read();
add(x,y,i);add(y,x,i);
}
puts("1");
dfs(1,1,n);a[1]=n;
for(int i=1;i<=n;i++)printf("%d ",a[i]);puts("");
for(int i=1;i<n;i++)printf("%d ",pos[i]);puts("");
memset(head,0,sizeof(head));tot=0;cnt=0;
}
return 0;
}
5.F. Jee, You See?
看着数据范围,就知道是数位dp
问题变成了,有多少种数组满足和小于等于X,异或和为Z的方案数=CAL(x)。
ans=CAL(R)-CAL(L-1)
点击查看代码
#include <bits/stdc++.h>
#define ll long long
#define pa pair<ll,int>
using namespace std;
const int maxn=3e5+101;
const int MOD=1e9+7;
const int inf=2147483647;
const double pi=acos(-1);
const double eps=1e-12;
ll read(){
ll x=0,f=1;char ch=getchar();
for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1;
for(;isdigit(ch);ch=getchar())x=x*10+ch-'0';
return x*f;
}
ll n,L,R,z,c[3001][3001];
ll cal(ll x){
vector<ll>dp(n+1,0);
dp[0]=1;
for(int k=60;k>=0;k--){
vector<ll>new_dp(n+1,0);
for(int i=0;i<=n;i++){
if(!dp[i])continue;
ll limi=2*i+((x>>k)&1);
for(int j=(z>>k)&1;j<=min(n,limi);j+=2){
(new_dp[min(n,limi-j)]+=dp[i]*c[n][j]%MOD)%=MOD;
}
}
dp=new_dp;
}
ll ans=0;
for(auto i:dp)(ans+=i)%=MOD;
return ans;
}
int main(){
n=read();L=read();R=read();z=read();
for(int i=0;i<=n;i++)c[i][0]=1;
for(int i=1;i<=n;i++){
for(int j=1;j<=i;j++)c[i][j]=(c[i-1][j]+c[i-1][j-1])%MOD;
}
printf("%lld",((cal(R)-cal(L-1))%MOD+MOD)%MOD);
return 0;
}
6.E - Prefix Equality
大致题意就是每次询问给定x,y
问\({a_1,a_2,····,a_x}和{b_1,b_2,····,b_y}\)的集合是否一样
显然当两个集合内的个数不同肯定就是NO
当集合个数相同时,就得判断里面的数是否相同
不妨设
aa[i]表示a中前i个数一共有aa[i]个不同的数
aaa[i]表示前i个不同的数第i个不同的是多少
b同理
那么假设\({a_1,a_2,····,a_x}和{b_1,b_2,····,b_y}\)的集合个数一样
也就是aa[x]==bb[y]=k
那么判断aaa[k]和bbb[k]的前缀的集合是否相同,用jud数组来记录
这样就能O(1)判断了
点击查看代码
#include<bits/stdc++.h>
#define ll long long
#define pa pair<ll,int>
using namespace std;
const int maxn=3e5;
const int MOD=998244353;
const int inf=2147483647;
const double pi=acos(-1);
const double eps=1e-12;
int read(){
int x=0,f=1;char ch=getchar();
for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1;
for(;isdigit(ch);ch=getchar())x=x*10+ch-'0';
return x*f;
}
int qq,n,a[maxn],b[maxn],aa[maxn],bb[maxn],aaa[maxn],bbb[maxn];
bool jud[maxn];
int main(){
n=read();
set<int>s;
int tot1=0,tot2=0;
for(int i=1;i<=n;i++){
a[i]=read();
if(!s.count(a[i]))tot1++,aaa[tot1]=a[i];
aa[i]=tot1;
s.insert(a[i]);
}
s.clear();
for(int i=1;i<=n;i++){
b[i]=read();
if(!s.count(b[i]))tot2++,bbb[tot2]=b[i];
bb[i]=tot2;
s.insert(b[i]);
}
set<int>s1,s2;
for(int i=1;i<=min(tot1,tot2);i++){
s1.insert(aaa[i]);
s2.insert(bbb[i]);
if(s1==s2)jud[i]=1;
else jud[i]=0;
}
for(int i=min(tot1,tot2)+1;i<=max(tot1,tot2);i++)jud[i]=0;
qq=read();
for(int i=1;i<=qq;i++){
int l=read(),r=read();
if(aa[l]==bb[r] && jud[aa[l]])puts("Yes");
else puts("No");
}
return 0;
}
点击查看代码
#include<bits/stdc++.h>
#define ll long long
#define pa pair<ll,int>
using namespace std;
const int maxn=3e5;
const int MOD=998244353;
const int inf=2147483647;
const double pi=acos(-1);
const double eps=1e-12;
mt19937_64 mrand(random_device{}());
#define u64 unsigned long long
int read(){
int x=0,f=1;char ch=getchar();
for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1;
for(;isdigit(ch);ch=getchar())x=x*10+ch-'0';
return x*f;
}
int qq,n,a[maxn];
u64 ha[maxn],hb[maxn];
int main(){
n=read();
set<int>s;
map<int,u64>cnt;
ha[0]=hb[0]=0;
for(int i=1;i<=n;i++){
a[i]=read();ha[i]=ha[i-1];
if(!cnt.count(a[i]))cnt[a[i]]=mrand();
if(!s.count(a[i]))ha[i]+=cnt[a[i]];
s.insert(a[i]);
}
s.clear();
for(int i=1;i<=n;i++){
a[i]=read();hb[i]=hb[i-1];
if(!cnt.count(a[i]))cnt[a[i]]=mrand();
if(!s.count(a[i]))hb[i]+=cnt[a[i]];
s.insert(a[i]);
}
qq=read();
for(int i=1;i<=qq;i++){
int l=read(),r=read();
if(ha[l]==hb[r])puts("Yes");
else puts("No");
}
return 0;
}
题目大意: 给出若干天的股票价格,每天你可以买一股或卖一股,求出 n天后的最大利润。
模拟费用流(反悔贪心)
两种操作:
1.找到之前没操作的并且股票最便宜的一天,在那天买入,今天卖出
2.将之前的某一次操作反悔。比如今天是第c天,之前有一个操作:在第a天买入第b天卖出,将在第b天卖出这个操作反悔,不在第b天卖出,而是在第c天卖出,然后标记第b天没有被操作过。
点击查看代码
#include<bits/stdc++.h>
#define ll long long
#define pa pair<ll,int>
using namespace std;
const int maxn=3e5;
const int MOD=998244353;
const int inf=2147483647;
const double pi=acos(-1);
const double eps=1e-12;
mt19937_64 mrand(random_device{}());
#define u64 unsigned long long
int read(){
int x=0,f=1;char ch=getchar();
for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1;
for(;isdigit(ch);ch=getchar())x=x*10+ch-'0';
return x*f;
}
int main(){
priority_queue<int>q;
ll ans=0;
int n=read();
for(int i=1;i<=n;i++){
int x=read();
q.push(-x);//操作1
if(x+q.top()<0)continue;
ans+=x+q.top();q.pop();
q.push(-x);//操作2
}
printf("%lld",ans);
return 0;
}
8.D - At Most 3 (Contestant ver.)
有意思的一道题
考虑每个数只能由3个数相加而成,表示范围为1~1000000,且不超过300个数、
不妨考虑3个数,每个数来构成2位
1 ~ 99,100 ~ 9900,10000~990000
这样能构成1~999999的每一个数,最后额外加入一个1000000
点击查看代码
#include <bits/stdc++.h>
#define ll long long
#define pa pair<ll,int>
using namespace std;
const int maxn=3e6+101;
const int MOD=998244353;
const int inf=2147483647;
const double pi=acos(-1);
const double eps=1e-12;
int read(){
int x=0,f=1;char ch=getchar();
for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1;
for(;isdigit(ch);ch=getchar())x=x*10+ch-'0';
return x*f;
}
int a[maxn];
int main(){
int n=read();
vector<int>ans;
for(int i=1;i<=99;i++){
ans.push_back(i);
ans.push_back(i*100);
ans.push_back(i*10000);
}
ans.push_back(1000000);
printf("%d\n",ans.size());
for(auto i:ans)printf("%d ",i);
return 0;
}
9.F - Two Spanning Trees
大致题意就是给m条边,构造两种生成树
第一种就是非树边都是返祖边,这个用dfs就可以
因为dfs时会把所有当前点的横插边都选完,剩下没选的就是返祖边了
第二种就是非树边都是横插边,这个用bfs就可以
因为bfs时会把所有返祖边都选完,剩下的就是横插边
可以画个图试试
点击查看代码
#include <bits/stdc++.h>
#define ll long long
#define pa pair<ll,int>
using namespace std;
const int maxn=3e6+101;
const int MOD=998244353;
const int inf=2147483647;
const double pi=acos(-1);
const double eps=1e-12;
int read(){
int x=0,f=1;char ch=getchar();
for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1;
for(;isdigit(ch);ch=getchar())x=x*10+ch-'0';
return x*f;
}
int n,m;
int tot,head[maxn],nx[maxn],to[maxn];
struct edge{
int x,y;
}a[maxn];
void add(int x,int y){to[++tot]=y;nx[tot]=head[x];head[x]=tot;}
vector<int>ans;
int book[maxn];
void dfs(int x){
book[x]=1;
for(int i=head[x];i;i=nx[i]){
if(book[to[i]])continue;
ans.push_back((i+1)/2);
dfs(to[i]);
}
return ;
}
void bfs(int x){
queue<int>q;
q.push(1);book[1]=1;
while(!q.empty()){
int u=q.front();q.pop();
for(int i=head[u];i;i=nx[i]){
if(book[to[i]])continue;
ans.push_back((i+1)/2);
q.push(to[i]);
book[to[i]]=1;
}
}
return ;
}
int main(){
n=read();m=read();
for(int i=1;i<=m;i++){
int x=read(),y=read();
add(x,y);add(y,x);
a[i].x=x;a[i].y=y;
}
// puts("____________");
dfs(1);
for(auto i:ans)printf("%d % d\n",a[i].x,a[i].y);
// puts("____________");
ans.clear();memset(book,0,sizeof(book));
bfs(1);
for(auto i:ans)printf("%d %d\n",a[i].x,a[i].y);
return 0;
}
10.永久流放
点击查看代码
#include <bits/stdc++.h>
#define ll long long
#define pa pair<ll,int>
using namespace std;
const int maxn=3e6+101;
const int MOD=998244353;
const int inf=2147483647;
const double pi=acos(-1);
const double eps=1e-12;
int read(){
int x=0,f=1;char ch=getchar();
for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1;
for(;isdigit(ch);ch=getchar())x=x*10+ch-'0';
return x*f;
}
ll power(ll x,ll y){
ll ans=1;
while(y){
if(y&1)ans=ans*x%MOD;
x=x*x%MOD;y>>=1;
}
return ans;
}
int n,m;
ll dp[maxn];
int main(){
n=read();m=read();
dp[n*m]=1;
ll pre=1;
for(int i=n*m-1;i>=1;i--){
pre-=dp[i+n];pre%=MOD;
dp[i]=1+power(n-1,MOD-2)*pre%MOD;
dp[i]%=MOD;
pre=pre+dp[i];
pre%=MOD;
}
printf("%lld",(dp[1]%MOD+MOD)%MOD);
return 0;
}