StarSilk 题单笔记-2600
Three Days Grace
如果想到一个很复杂/正确性可疑的贪心,那就应该考虑一下 DP 了。
一个经典套路是递减枚举最小值,维护最小化的最大值,就是双指针。
考虑 \(dp_i\) 是数 \(i\) 在当前最小值约束下拆分后最小化的最大值。
首先,当最小值从 \(x+1 \to x\) 时,只有 \(x\) 的倍数 \(dp\) 值可能会被更新。
更具体地说,此时我们需要处理的是如下的转移:
if (y>=x*x) dp[y]=min(dp[y],dp[y/x]);
显然这样的转移个数是调和级数的。
答案就是对每个 \(x \leq \min a\) 处的 \(\min dp - x\) 取 \(\min\)。
怎么求 \(\min dp\) 呢?
我们对 \(a\) 中数对应的 \(dp\) 值打上 tag,每次转移时如果影响到 \(a\) 中的数就挪一下 tag,并把右指针左移到第一个有 tag 的位置就好。(利用双指针的单调性)
Code
void R()
{
int n,m,mn,mx,ans=1e9;
cin>>n>>m;
mx=m;
vector<int> a(n),dp(m+1),vis(m+1),fl(m+1);
iota(dp.begin(),dp.end(),0);
for (int &x:a)
{
cin>>x;
fl[x]=vis[x]=1;
}
mn=*min_element(a.begin(),a.end());
for (int i=m;i>=1;i--)
{
for (i64 j=1ll*i*i;j<=m;j+=i)
{
if (vis[j]) fl[dp[j]]--;
dp[j]=min(dp[j],dp[j/i]);
if (vis[j]) fl[dp[j]]++;
}
while (!fl[mx]) mx--;
if (i<=mn) ans=min(ans,mx-i);
}
cout<<ans<<'\n';
return;
}
i*i
可能爆 int
,切记切记。
Latin Square
我们可以把矩阵 \(a\) 看成 \(\mathcal{O}(n^2)\) 个三元组信息:\(( i,j,a_{i,j} )\),于是各个操作分别变成加减某维或交换某维。
于是我们 \(\mathcal{O}(m)\) 模拟操作,记录下三维在操作后是谁,然后 \(\mathcal{O}(n^2)\) 还原即可。
感觉实现还是很有趣的
void R()
{
int n,m;
string s;
cin>>n>>m;
vector<vector<int>> mat(n,vector<int>(n)),opt(mat);
for (int i=0;i<n;i++)
for (int j=0;j<n;j++)
{
cin>>mat[i][j];
mat[i][j]--;
}
cin>>s;
array<int,3> from={0,1,2},val={0,0,0};
for (int i=0;i<m;i++)
{
if (s[i]=='U') val[0]--;
else if (s[i]=='D') val[0]++;
else if (s[i]=='L') val[1]--;
else if (s[i]=='R') val[1]++;
else if (s[i]=='I')
{
swap(from[1],from[2]);
swap(val[1],val[2]);
}
else
{
swap(from[0],from[2]);
swap(val[0],val[2]);
}
}
for (int i=0;i<n;i++)
for (int j=0;j<n;j++)
{
auto solve=[&](int op)->int
{
if (op==0) return i;
if (op==1) return j;
return mat[i][j];
};
auto get=[&](int id)->int
{
return ((solve(from[id])+val[id])%n+n)%n;
};
opt[get(0)][get(1)]=get(2);
}
for (int i=0;i<n;i++)
{
for (int j=0;j<n;j++)
{
cout<<opt[i][j]+1;
if (j+1!=n) cout<<' ';
}
cout<<'\n';
}
cout<<'\n';
return;
}