11.26 NOIP2024模拟赛#28 div1
倒数力,大悲
T1
看样例+手推想到一个贪心,就是隔一个选一下
然鹅发现大小关系没有传递性
然后码的暴力
正解是尽量给短的较大的,如果两边长短不一样就把多的补给长的那一个
然后证明不知道(
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define ll long long
#define fd(i,a,b) for(int i=(a);i<=(b);i=-~i)
#define bd(i,a,b) for(int i=(a);i>=(b);i=~-i)
#define db(x) cout<<"DEBUG "<<#x<<" = "<<x<<endl;
#define endl '\n'
using namespace std;
//#define SIZE (1<<20)
//char In[SIZE],Out[SIZE],*p1=In,*p2=In,*p3=Out;
//#define getchar() (p1==p2&&(p2=(p1=In)+fread(In,1,SIZE,stdin),p1==p2)?EOF:*p1++)
inline int read()
{
int x=0,f=1;char c=getchar();
while(c<'0'||c>'9'){if(c=='-') f=-1;c=getchar();}
while(c>='0'&&c<='9'){x=x*10+(c-48);c=getchar();}
return x*f;
}
template<typename _T>
inline void write(_T x)
{
static _T sta[35];int top=0;
if(x<0) putchar('-'),x=-x;
do{sta[top++]=x%10,x/=10;}while(x);
while(top) putchar(sta[--top]+'0');
}
template<int len>
struct __int
{
int num[len*2+5];
__int(){memset(num,0,sizeof(num));}
inline void clear(){memset(num,0,sizeof(num));}
inline __int operator+(__int y)
{
__int<len> res;
fd(i,0,len)
{
res.num[i]+=num[i]+y.num[i];
res.num[i+1]+=res.num[i]/10;
res.num[i]%=10;
}
return res;
}
inline __int operator+(int y)
{
__int<len> res;
res.num[0]=y;
fd(i,0,len)
{
res.num[i]+=num[i];
res.num[i+1]+=res.num[i]/10;
res.num[i]%=10;
}
return res;
}
inline __int operator*(int y)
{
__int<len> res;
fd(i,0,len)
{
res.num[i]+=y*num[i];
res.num[i+1]+=res.num[i]/10;
res.num[i]%=10;
}
return res;
}
inline __int operator*(__int y)
{
__int<len> res;
fd(i,0,len)
{
fd(j,0,i) res.num[i]+=y.num[j]*num[i-j];
res.num[i+1]+=res.num[i]/10;
res.num[i]%=10;
}
return res;
}
inline __int operator/(int y)
{
__int<len>res=(*this);
bd(i,len,0)
{
res.num[i-1]+=res.num[i]%y*10;
res.num[i]/=y;
}
return res;
}
inline bool operator<(__int y)
{
bd(i,len,0) if(y.num[i]!=num[i]) return num[i]<y.num[i];
return 0;
}
inline bool operator==(__int b)
{
return (!(b<(*this)))&&(!((*this)<b));
}
inline void debug()
{
int mxl=0;
bd(i,len,0) if(num[i]) {mxl=i;break;}
bd(i,mxl,0) cerr<<num[i];
cerr<<endl;
}
inline void print()
{
int mxl=0;
bd(i,len,0) if(num[i]) {mxl=i;break;}
bd(i,mxl,0) putchar(num[i]+'0');
putchar('\n');
}
};
const int N=1e3+509,M=1e6+509,mod=998244353;
int a,b,c[N];
__int<N> aa,bb,ans;
inline void init()
{
a=read(),b=read();
if(a>b) swap(a,b);
fd(i,1,9) c[i]=read();
}
void solve1()
{
aa.clear(),bb.clear();
int len1=a,len2=b,fl=0;
for(int i=9;i;)
{
while(!c[i]&&i) --i;
if(!i) break;
--c[i];
if(!len1||fl) bb.num[--len2]=i;
else aa.num[--len1]=i;
while(!c[i]&&i) --i;
if(!i) break;
--c[i];
if(!len1||!fl) bb.num[--len2]=i;
else aa.num[--len1]=i;
if(aa.num[len1]>bb.num[len2]) fl=1;
// aa.debug(),bb.debug();
}
ans.clear();
ans=aa*bb;
ans.print();
}
inline void Main()
{
init();
solve1();
}
signed main()
{
#define FJ
#ifdef FJ
freopen("math.in","r",stdin);
freopen("math.out","w",stdout);
#else
// freopen("ex_math1.in","r",stdin);
// freopen("math.out","w",stdout);
#endif
//#define io
#ifdef io
ios::sync_with_stdio(0);
cin.tie(0); cout.tie(0);
#endif
int T=read();
while(T--) Main();
return 0;
}
T2
很有思路,发现缩完点后一个联通块里可以互相推荐,然后 拓排+DP 即可
然后记 \(sum[i][j]\) 表示第 \(i\) 个联通块里选 \(j\) 个的最小值
\(h[i][j]\) 表示走到第 \(i\) 个块,已经选了 \(j\) 个的最小值
\(f[i][j]\) 表示在 \(i\) 块内出发并且在块内走了 \(j\) 个的最小值
转移的话就是:
然鹅我把
当你到达一个餐厅 \(i\),如果你持有这个餐厅老板推荐的店的用餐记录,你必须支付 \(x_i\) 块钱,否则需要支付 \(y_i\) 块钱。
理解成了:
当你到达一个餐厅 \(i\),如果你是被某个餐厅老板推荐来的,你必须支付 \(x_i\) 块钱,否则需要支付 \(y_i\) 块钱。
然后我还认为这个复杂度是 \(O(n^3)\) 的,所以码到一半就去码暴力了
正解其实就是这个,只不过是因为 \(O(n^3)\) 跑不满所以可以过
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define ll long long
#define fd(i,a,b) for(int i=(a);i<=(b);i=-~i)
#define bd(i,a,b) for(int i=(a);i>=(b);i=~-i)
#define db(x) cout<<"DEBUG "<<#x<<" = "<<x<<endl;
#define endl '\n'
using namespace std;
//#define SIZE (1<<20)
//char In[SIZE],Out[SIZE],*p1=In,*p2=In,*p3=Out;
//#define getchar() (p1==p2&&(p2=(p1=In)+fread(In,1,SIZE,stdin),p1==p2)?EOF:*p1++)
inline int read()
{
int x=0,f=1;char c=getchar();
while(c<'0'||c>'9'){if(c=='-') f=-1;c=getchar();}
while(c>='0'&&c<='9'){x=x*10+(c-48);c=getchar();}
return x*f;
}
const int N=1e3+509,M=1e6+509,mod=998244353;
int n,m,k,ans[N];
int a[N],b[N];
int head[N],tot;
struct edge{int y,nxt;}e[M<<1];
inline void adde(int x,int y)
{
e[++tot]={y,head[x]},head[x]=tot;
}
int cnt,dfn[N],low[N];
int st[N],top,ins[N];
vector<int> scc[N];
int sccsum,c[N],siz[N];
void tarjan(int x)
{
dfn[x]=low[x]=++cnt;
st[++top]=x,ins[x]=1;
for(int i=head[x];i;i=e[i].nxt)
{
int y=e[i].y;
if(!dfn[y])
{
tarjan(y);
low[x]=min(low[x],low[y]);
}
else if(ins[y]) low[x]=min(low[x],dfn[y]);
}
if(dfn[x]==low[x])
{
++sccsum;
scc[sccsum].push_back(0);
int y;
do
{
y=st[top--],ins[y]=0;
c[y]=sccsum,
scc[sccsum].push_back(y);
}while(x!=y);
siz[sccsum]=scc[sccsum].size()-1;
}
}
vector<int> g[N];
int in[N];
bool fl[N];
void nw(int xx)
{
fd(i,1,sccsum) fl[i]=0;
fl[xx]=1;
for(auto &x:scc[xx])
{
for(int i=head[x];i;i=e[i].nxt)
{
int y=c[e[i].y];
if(!fl[y]) fl[y]=1,g[xx].push_back(y),++in[y];
}
}
}
int f[N][N],h[N][N],sum[N][N],p[M];
void pre()
{
fd(i,1,sccsum)
{
sort(scc[i].begin(),scc[i].end(),[&](int x,int y){return a[x]<a[y];});
fd(j,1,siz[i]) sum[i][j]=sum[i][j-1]+a[scc[i][j]],p[scc[i][j]]=j;
}
}
void topu()
{
queue<int> q;
fd(i,1,sccsum)
{
fd(j,1,n) f[i][j]=h[i][j]=1e18;
for(auto &x:scc[i])
{
if(x)
{
fd(j,0,siz[i]-1)
f[i][j+1]=min(f[i][j+1],(p[x]>j?sum[i][j]:sum[i][j+1]-a[x])+b[x]);
}
}
if(!in[i]) q.push(i);
}
while(!q.empty())
{
int x=q.front();q.pop();
bd(i,n,0) fd(j,0,min(siz[x],i))
h[x][i]=min(h[x][i],h[x][i-j]+f[x][j]);
for(auto &y:g[x])
{
fd(k,0,n) h[y][k]=min(h[y][k],h[x][k]);
if(!--in[y]) q.push(y);
}
}
}
inline void solve()
{
fd(i,1,n) if(!dfn[i]) tarjan(i);
pre();
fd(i,1,sccsum) nw(i);
topu();
fd(i,1,n) ans[i]=1e18;
fd(i,1,sccsum) fd(j,1,n) ans[j]=min(ans[j],h[i][j]);
fd(i,1,n) if(ans[i]<1e18) printf("%lld\n",ans[i]);
}
signed main()
{
#define FJ
#ifdef FJ
freopen("food.in","r",stdin);
freopen("food.out","w",stdout);
#else
// freopen("A.in","r",stdin);
// freopen("A.out","w",stdout);
#endif
//#define io
#ifdef io
ios::sync_with_stdio(0);
cin.tie(0); cout.tie(0);
#endif
n=read();
fd(i,1,n)
{
a[i]=read(),b[i]=read();
int d=read();
fd(j,1,d)
{
int y=read();
adde(i,y);
}
}
solve();
return 0;
}
T3
没看题(
正解是状压
先把初始状态和目标状态异或一下
然后注意到答案应该不会太大,而且是随机数据
定义 \(f_{i,s}\) 表示到第 \(i\) 秒,点的颜色状压为 \(s\),是否可行
复杂度 \(O(qn^2 2^n)\),然鹅跑不满(
T4
没看题(
正解是(复制的,防丢):
点击查看
首先考虑 \(c=0\) 的时候怎么做,\(mex\) 尽量大并且权值是一个排列,换句话说就是从权值为 \(0\) 的点开始加入一个集合 \(S\),不断加入权值为 \(1,2,3…\) 的点,使得集合中的点一直在一条路径上,直到加不动为止。
那么现在的问题是,我们加入一个点 \(x\) 的时候,怎么判断加入 \(x\) 后集合内的点是否还能在同一条路径上呢?首先设这条路径起点为 \(s\),终点为 \(t\),那么第一种情况是 \(x\) 在 \(s→t\) 的路径上,第二种情况是 \(x\) 变成起点或者终点了,第三种情况就是不合法,\(x\) 无法加入集合中。
前两种情况有一个非常高明的方法求得,首先我们求出这三个点的“交叉点”——即 \(s→x\) 的路径,\(s→t\) 的路径和 \(x→t\) 的路径共同经过的那个点,可以看出交叉点唯一,那么交叉点是 \(x\),那么就是第一种情况,交叉点是 \(s\) 或 \(t\),就是第二种情况,否则就是第三种情况。
据此,我们就解决了 \(c=0\) 的情况。
然后我们再依次解决 \(c=1,2,3…\) 的情况,当 \(c\) 减少 \(1\) 的时候,其实我们只需要删掉权值为 \(c\) 的点,并且重新进行 \(c=0\) 的过程即可。
那么现在的问题就是如何在集合内删掉一个点。
我们再回看进行上述过程需要这个集合支持的操作是:删掉一个点,加入一个点,维护起点和终点。首先如果删掉了起点和终点,我们需要快速找到新的起点和终点。那么我们就可以用一个 set 来维护整条路径,点的比较权值是与起点的距离。
这样如果插入一个点(非起点)的话,求一个与起点的距离加入即可,删除也是直接删。加入或删掉起点的话,距离新的起点的距离会变化,不过是整体加减一个值,在外部维护一个 lazy 标记表示当前集合内的距离与真实值的 \(Δ\) 即可。
由于一个点最多会被加入 \(2\) 次删除 \(2\) 次,每次加删的复杂度是一个 \(log\) 的(set
),总复杂度 \(O(n \log n)\)。
实际上这道题存在 \(O(n)\) 做法。
首先我们需要一个 \(O(n)\) 预处理,\(O(1)\) 查询的 lca 算法,这可以通过初赛学到的四毛子优化解决。
考虑 \(c=0\) 时的做法,不去维护 \(set\),而只维护起点和终点,那么加入的时间复杂度为 \(O(1)\)。
但是这样我们就没有办法删除了,该怎么办呢?
使用滑动窗口维护半群信息的 \(trick\),用两个栈去模拟队列。
具体来说,假设当前的队列是 \([l,r]\),我们会选择一个分界点,以分界点为栈底往左往右建立栈,维护出每个元素到栈底这段区间对应路径的起点和终点。
每次 \(r\) 往右移就往右边的栈中压入元素,\(l\) 往右移就弹出左边的栈的元素。
如果弹栈时栈已经空了怎么办?
以右端点为栈底,从右往左建立新的栈,此时左边的栈含有队列内所有元素,而右边的栈中为空。
注意每个元素最多会进入左边的栈两次,进入右边的栈两次,所以总时间复杂度为 \(O(n)\)。
总结
- T1 应该再找找规律,其实赛时已经接近正解了
- 认真审题!!!
敢闯敢试敢为天下先(T2 不敢写+看错题,其实赛时已经推出来柿子了) - 不要在一道题上浪费过多时间,尽量码完暴力
本文来自博客园,作者:whrwlx,转载请注明原文链接:https://www.cnblogs.com/whrwlx/p/18570478