前言
byd 感觉很抽象啊,三题表现分才 1400,以前哪有那么多人四题的......
一、A. Lawn Mower
cpp
#include <bits/stdc++.h>
using namespace std;
/* /\_/\
* (= ._.)
* / > \>
*/
/*
*想好再写
*注意审题 注意特判
*不要红温 不要急躁 耐心一点
*WA了不要立马觉得是思路不对 先耐心找反例
*/
#define dbg(x) cout<<#x<<endl;cout<<x<<endl;
#define vdbg(a) cout<<#a<<endl;for(auto x:a)cout<<x<<" ";cout<<endl;
#define YES cout<<"YES"<<endl;return ;
#define Yes cout<<"Yes"<<endl;return ;
#define NO cout<<"NO"<<endl;return ;
#define No cout<<"No"<<endl;return ;
#define endl '\n'
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
const int INF=1e9;
const ll INFLL=1e18;
const int dx[]={-1,1,0,0};
const int dy[]={0,0,-1,1};
const int ddx[]={-2,-1,1,2,2,1,-1,-2};
const int ddy[]={1,2,2,1,-1,-2,-2,-1};
void solve()
{
ll n,w;
cin>>n>>w;
cout<<n-n/w<<endl;
}
void init()
{
}
signed main()
{
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int t=1;
cin>>t;
init();
while(t--)
{
solve();
}
return 0;
}
感觉没啥好说的,能放的最少木板数就是 n/w,所以能删掉的就是 n-n/w。
二、B. Offshores
byd 感觉这个 B 真不简单啊为什么都写那么快......
cpp
#include <bits/stdc++.h>
using namespace std;
/* /\_/\
* (= ._.)
* / > \>
*/
/*
*想好再写
*注意审题 注意特判
*不要红温 不要急躁 耐心一点
*WA了不要立马觉得是思路不对 先耐心找反例
*/
#define dbg(x) cout<<#x<<endl;cout<<x<<endl;
#define vdbg(a) cout<<#a<<endl;for(auto x:a)cout<<x<<" ";cout<<endl;
#define YES cout<<"YES"<<endl;return ;
#define Yes cout<<"Yes"<<endl;return ;
#define NO cout<<"NO"<<endl;return ;
#define No cout<<"No"<<endl;return ;
#define endl '\n'
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
const int INF=1e9;
const ll INFLL=1e18;
const int dx[]={-1,1,0,0};
const int dy[]={0,0,-1,1};
const int ddx[]={-2,-1,1,2,2,1,-1,-2};
const int ddy[]={1,2,2,1,-1,-2,-2,-1};
void solve()
{
ll n,x,y;
cin>>n>>x>>y;
vector<ll>a(n+1);
for(int i=1;i<=n;i++)
{
cin>>a[i];
}
vector<ll>pre(n+1);
for(int i=1;i<=n;i++)
{
pre[i]+=pre[i-1]+a[i]/x*y;
}
vector<ll>suf(n+2);
for(int i=n;i>=1;i--)
{
suf[i]+=suf[i+1]+a[i]/x*y;
}
ll ans=0;
for(int i=1;i<=n;i++)
{
ans=max(ans,pre[i-1]+a[i]+suf[i+1]);
}
cout<<ans<<endl;
}
void init()
{
}
signed main()
{
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int t=1;
cin>>t;
init();
while(t--)
{
solve();
}
return 0;
}
因为每次转钱都是只亏不赚的,所以能一次转完就不会选择二传,况且第一次没有限制可以往任意位置转。那么就只需要枚举哪个位置是最后转到的位置,然后让左右两边都转给这个位置,这个就只需要用一个前后缀维护,每次合并前后缀取最大值即可。
三、C. Secret message
是不是都是 ai 啊,这 C 怎么能过那么多人的......
cpp
#include <bits/stdc++.h>
using namespace std;
/* /\_/\
* (= ._.)
* / > \>
*/
/*
*想好再写
*注意审题 注意特判
*不要红温 不要急躁 耐心一点
*WA了不要立马觉得是思路不对 先耐心找反例
*/
#define dbg(x) cout<<#x<<endl;cout<<x<<endl;
#define vdbg(a) cout<<#a<<endl;for(auto x:a)cout<<x<<" ";cout<<endl;
#define YES cout<<"YES"<<endl;return ;
#define Yes cout<<"Yes"<<endl;return ;
#define NO cout<<"NO"<<endl;return ;
#define No cout<<"No"<<endl;return ;
#define endl '\n'
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
const int INF=1e9;
const ll INFLL=1e18;
const int dx[]={-1,1,0,0};
const int dy[]={0,0,-1,1};
const int ddx[]={-2,-1,1,2,2,1,-1,-2};
const int ddy[]={1,2,2,1,-1,-2,-2,-1};
void solve()
{
int n,k;
cin>>n>>k;
vector<string>a(k+1);
for(int i=1;i<=k;i++)
{
cin>>a[i];
a[i]=" "+a[i];
}
vector<set<char>>st(n+1);
for(int i=1;i<=n;i++)
{
for(int j=1;j<=k;j++)
{
st[i].insert(a[j][i]);
}
}
vector<int>fac;
for(int i=1;i*i<=n;i++)
{
if(n%i==0)
{
fac.push_back(i);
if(i!=n/i)
{
fac.push_back(n/i);
}
}
}
sort(fac.begin(),fac.end());
auto check=[&](int len)->bool
{
for(int l=1;l<=len;l++)
{
bool found=false;
for(int i=1;i<=k;i++)
{
char cur=a[i][l];
bool ok=true;
for(int j=l+len;j<=n;j+=len)
{
if(st[j].find(cur)==st[j].end())
{
ok=false;
break;
}
}
if(ok)
{
found=true;
break;
}
}
if(!found)
{
return false;
}
}
return true;
};
int res;
for(auto &x:fac)
{
if(check(x))
{
res=x;
break;
}
}
vector<char>ans(n+1);
for(int l=1;l<=res;l++)
{
for(int i=1;i<=k;i++)
{
char cur=a[i][l];
ans[l]=cur;
bool ok=true;
for(int j=l+res;j<=n;j+=res)
{
if(st[j].find(cur)==st[j].end())
{
ok=false;
break;
}
ans[j]=cur;
}
if(ok)
{
break;
}
}
}
for(int i=1;i<=n;i++)
{
cout<<ans[i];
}
cout<<endl;
}
void init()
{
}
signed main()
{
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int t=1;
cin>>t;
init();
while(t--)
{
solve();
}
return 0;
}
首先不难想到,可以枚举所有可能的循环节长度,然后 check 能否拼出来。但这样复杂度就是 O(n^2) 的了,所以需要考虑优化。
因为是重复拼,所以可以想到只有循环节长度为 n 的因数才有可能达成。那么就可以考虑先预处理出所有 n 的因数,那么就有一个比较常见的 Trick:对于数字 n,其因数个数是 O(log n) 级别的。
那么就可以预处理出 n 的每个因数,然后从小到大枚举每个因数,然后 check 看能否达成,能的话直接 break 即可。对于 check 方法,那么就是考虑枚举每个位置在循环节中对应的位置。然后对于每个循环节位置,枚举第一个循环节当前位置的每个字符,然后考虑在选当前字符的情况下,去后续看是否每个位置在所有串中都存在这个字符,这个可以通过用 set 预处理来实现。
在找到答案后,只需要按上述相同的方法过一遍,每次找到的话直接往答案里填字符即可。
四、D. Table Cut
cpp
#include <bits/stdc++.h>
using namespace std;
/* /\_/\
* (= ._.)
* / > \>
*/
/*
*想好再写
*注意审题 注意特判
*不要红温 不要急躁 耐心一点
*WA了不要立马觉得是思路不对 先耐心找反例
*/
#define dbg(x) cout<<#x<<endl;cout<<x<<endl;
#define vdbg(a) cout<<#a<<endl;for(auto x:a)cout<<x<<" ";cout<<endl;
#define YES cout<<"YES"<<endl;return ;
#define Yes cout<<"Yes"<<endl;return ;
#define NO cout<<"NO"<<endl;return ;
#define No cout<<"No"<<endl;return ;
#define endl '\n'
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
const int INF=1e9;
const ll INFLL=1e18;
const int dx[]={-1,1,0,0};
const int dy[]={0,0,-1,1};
const int ddx[]={-2,-1,1,2,2,1,-1,-2};
const int ddy[]={1,2,2,1,-1,-2,-2,-1};
void solve()
{
int n,m;
cin>>n>>m;
vector<vector<int>>g(n+1,vector<int>(m+1));
ll sum=0;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
cin>>g[i][j];
sum+=g[i][j];
}
}
cout<<(sum/2)*(sum-sum/2)<<endl;
queue<pii>q;
vector<vector<int>>vis(n+1,vector<int>(m+1));
vector<int>dx={-1,0};
vector<int>dy={0,1};
int x=n,y=1;
q.push({x,y});
vis[x][y]=1;
int ans=g[x][y];
while(!q.empty())
{
auto [x,y]=q.front();
q.pop();
if(ans==sum/2)
{
break;
}
for(int i=0;i<2;i++)
{
int nx=x+dx[i];
int ny=y+dy[i];
if(nx>=1&&nx<=n&&ny>=1&&ny<=m&&!vis[nx][ny])
{
ans+=g[nx][ny];
vis[nx][ny]=1;
q.push({nx,ny});
if(ans==sum/2)
{
break;
}
}
}
}
int px=1,py=1;
while(px<n+1||py<m+1)
{
if(px==n+1)
{
cout<<"R";
py++;
}
else if(py==m+1)
{
cout<<"D";
px++;
}
else
{
if(vis[px][py])
{
cout<<"R";
py++;
}
else
{
cout<<"D";
px++;
}
}
}
cout<<endl;
}
void init()
{
}
signed main()
{
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int t=1;
cin>>t;
init();
while(t--)
{
solve();
}
return 0;
}
Trick:对于网格从左上到右下分成两部分的问题,可以通过从左下角开始 bfs 来实现划分。
因为要让两部分 1 的个数的乘积最大,那么很容易想到当两部分 1 的个数一样时乘积最大。又因为 1 的个数在遍历的过程中是线性增加的,那么就说明必然存在一个位置使得 1 的个数正好一半一半。
那么就只需要从左下角开始 bfs,然后只要当前扫到的 1 的数量到了一半就退出。每次维护哪些格子被扫过了,注意只要够了就立马退出。
还原路径时,注意因为是沿网格线走,所以需要从 (0,0) 开始。之后当前点坐标所对应的格子坐标就是当前点右下角的格子了,那么就是若在 bfs 的过程中访问过就往右走,否则就往下走。
五、E. The Turtle Strikes Back
写之前: Div2 E?就这?
写之后:byd 确实有难度,经典思路十分钟调试一小时了......
cpp
#include <bits/stdc++.h>
using namespace std;
/* /\_/\
* (= ._.)
* / > \>
*/
/*
*想好再写
*注意审题 注意特判
*不要红温 不要急躁 耐心一点
*WA了不要立马觉得是思路不对 先耐心找反例
*/
#define dbg(x) cout<<#x<<endl;cout<<x<<endl;
#define vdbg(a) cout<<#a<<endl;for(auto x:a)cout<<x<<" ";cout<<endl;
#define YES cout<<"YES"<<endl;return ;
#define Yes cout<<"Yes"<<endl;return ;
#define NO cout<<"NO"<<endl;return ;
#define No cout<<"No"<<endl;return ;
#define endl '\n'
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
const int INF=1e9;
const ll INFLL=1e18;
const int dx[]={-1,1,0,0};
const int dy[]={0,0,-1,1};
const int ddx[]={-2,-1,1,2,2,1,-1,-2};
const int ddy[]={1,2,2,1,-1,-2,-2,-1};
void solve()
{
int n,m;
cin>>n>>m;
vector<vector<ll>>a(n+1,vector<ll>(m+1));
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
cin>>a[i][j];
}
}
vector<vector<ll>>sdp(n+1,vector<ll>(m+1,-INFLL));
sdp[1][1]=a[1][1];
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
if(i-1>=1)
{
sdp[i][j]=max(sdp[i][j],sdp[i-1][j]+a[i][j]);
}
if(j-1>=1)
{
sdp[i][j]=max(sdp[i][j],sdp[i][j-1]+a[i][j]);
}
}
}
vector<vector<ll>>edp(n+1,vector<ll>(m+1,-INFLL));
edp[n][m]=a[n][m];
for(int i=n;i>=1;i--)
{
for(int j=m;j>=1;j--)
{
if(i+1<=n)
{
edp[i][j]=max(edp[i][j],edp[i+1][j]+a[i][j]);
}
if(j+1<=m)
{
edp[i][j]=max(edp[i][j],edp[i][j+1]+a[i][j]);
}
}
}
auto val=[&](int i,int j)->ll
{
return sdp[i][j]+edp[i][j]-a[i][j];
};
vector<vector<ll>>pre(n+2,vector<ll>(m+2,-INFLL));
for(int i=n;i>=1;i--)
{
for(int j=1;j<=m;j++)
{
pre[i][j]=val(i,j);
if(i+1<=n)
{
pre[i][j]=max(pre[i][j],pre[i+1][j]);
}
if(j-1>=1)
{
pre[i][j]=max(pre[i][j],pre[i][j-1]);
}
}
}
vector<vector<ll>>suf(n+2,vector<ll>(m+2,-INFLL));
for(int i=1;i<=n;i++)
{
for(int j=m;j>=1;j--)
{
suf[i][j]=val(i,j);
if(i-1<=n)
{
suf[i][j]=max(suf[i][j],suf[i-1][j]);
}
if(j+1<=m)
{
suf[i][j]=max(suf[i][j],suf[i][j+1]);
}
}
}
ll ans=INFLL;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
ans=min(ans,max( val(i,j)-2*a[i][j],max(pre[i+1][j-1],suf[i-1][j+1]) ));
}
}
cout<<ans<<endl;
}
void init()
{
}
signed main()
{
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int t=1;
cin>>t;
init();
while(t--)
{
solve();
}
return 0;
}
因为要求选择一个位置取反,使得最大路径最小,那么其实是可以考虑去枚举选哪个位置取反的。
考虑定义 sdp[i][j] 为从 (1,1) 到 (i,j) 的最大权值累加和,再定义 edp[i][j] 为从 (n,m) 到 (i,j) 的最大权值累加和。那么此时就可以快速求出必须经过每个格子的最大累加和,有 val[i][j]=sdp[i][j]+edp[i][j]-a[i][j]。
之后对于选择当前位置取反,最大路径有两种可能,就是要么经过当前位置,要么不经过当前位置。首先,若最大路径经过当前位置,那么权值就是 val[i][j]-2*a[i][j]。之后,还是有Trick:若路径不经过当前格,那么就必须经过当前位置的左下或右上的某格。那么就只需要再类似二维前缀和那样预处理出左下角和右上角必须经过每个格子时的最大权值。
所以,当前位置取反时的最大路径就是经过和不经过当前格子的最大值,然后在全局取最小值就是答案。
六、F1. Again Trees... (Easy Version)
cpp
#include <bits/stdc++.h>
using namespace std;
/* /\_/\
* (= ._.)
* / > \>
*/
/*
*想好再写
*注意审题 注意特判
*不要红温 不要急躁 耐心一点
*WA了不要立马觉得是思路不对 先耐心找反例
*/
#define dbg(x) cout<<#x<<endl;cout<<x<<endl;
#define vdbg(a) cout<<#a<<endl;for(auto x:a)cout<<x<<" ";cout<<endl;
#define YES cout<<"YES"<<endl;return ;
#define Yes cout<<"Yes"<<endl;return ;
#define NO cout<<"NO"<<endl;return ;
#define No cout<<"No"<<endl;return ;
#define endl '\n'
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
const int INF=1e9;
const ll INFLL=1e18;
const int dx[]={-1,1,0,0};
const int dy[]={0,0,-1,1};
const int ddx[]={-2,-1,1,2,2,1,-1,-2};
const int ddy[]={1,2,2,1,-1,-2,-2,-1};
template<class T>
constexpr T power(T a, ll b) {
T res = 1;
for (; b != 0; b /= 2, a *= a) {
if (b & 1) {
res *= a;
}
}
return res;
}
template<int M>
struct ModInt {
public:
constexpr ModInt() : x(0) {}
template<typename T>
constexpr ModInt(T x_) {
T v = x_ % M;
if (v < 0) {
v += M;
}
x = v;
}
constexpr int val() const {
return x;
}
constexpr ModInt &operator++() & {
x++;
if (x == M) {
x = 0;
}
return *this;
}
constexpr ModInt operator++(int) & {
ModInt res = *this;
++(*this);
return res;
}
constexpr ModInt &operator--() & {
if (x == 0) {
x = M - 1;
} else {
x--;
}
return *this;
}
constexpr ModInt operator--(int) & {
ModInt res = *this;
--(*this);
return res;
}
constexpr ModInt operator-() const {
ModInt res;
res.x = (x == 0 ? 0 : M - x);
return res;
}
constexpr ModInt inv() const {
return power(*this, M - 2);
}
constexpr ModInt &operator*=(const ModInt &rhs) &{
x = ll(x) * rhs.val() % M;
return *this;
}
constexpr ModInt &operator+=(const ModInt &rhs) &{
x += rhs.val();
if (x >= M) {
x -= M;
}
return *this;
}
constexpr ModInt &operator-=(const ModInt &rhs) &{
x -= rhs.val();
if (x < 0) {
x += M;
}
return *this;
}
constexpr ModInt &operator/=(const ModInt &rhs) &{
return *this *= rhs.inv();
}
friend constexpr ModInt operator*(ModInt lhs, const ModInt &rhs) {
lhs *= rhs;
return lhs;
}
friend constexpr ModInt operator+(ModInt lhs, const ModInt &rhs) {
lhs += rhs;
return lhs;
}
friend constexpr ModInt operator-(ModInt lhs, const ModInt &rhs) {
lhs -= rhs;
return lhs;
}
friend constexpr ModInt operator/(ModInt lhs, const ModInt &rhs) {
lhs /= rhs;
return lhs;
}
friend constexpr bool operator==(ModInt lhs, const ModInt &rhs) {
return lhs.val() == rhs.val();
}
friend constexpr bool operator<(ModInt lhs, const ModInt &rhs) {
return lhs.val() < rhs.val();
}
friend constexpr bool operator>(ModInt lhs, const ModInt &rhs) {
return lhs.val() > rhs.val();
}
friend constexpr bool operator<=(ModInt lhs, const ModInt &rhs) {
return lhs.val() <= rhs.val();
}
friend constexpr bool operator>=(ModInt lhs, const ModInt &rhs) {
return lhs.val() >= rhs.val();
}
friend constexpr bool operator!=(ModInt lhs, const ModInt &rhs) {
return lhs.val() != rhs.val();
}
friend constexpr std::istream &operator>>(std::istream &is, ModInt &a) {
ll i;
is >> i;
a = i;
return is;
}
friend constexpr std::ostream &operator<<(std::ostream &os, const ModInt &a) {
return os << a.val();
}
private:
int x;
};
template<int M, typename T = ModInt<M>>
struct Comb {
vector<T> fac;
vector<T> inv;
Comb(int n) {
fac.assign(n, 1);
for(int i=1;i<n;i++)
{
fac[i]=fac[i-1]*i;
}
inv.assign(n, 1);
inv[n-1]=fac[n-1].inv();
for(int i=n-2;i>=0;i--)
{
inv[i]=inv[i+1]*(i+1);
}
}
template<std::signed_integral U>
T P(U n, U m) {
if(n<m)
{
return 0;
}
return fac[n] * inv[n - m];
}
template<std::signed_integral U>
T C(U n, U m) {
if(n<m||m<0)
{
return 0;
}
return fac[n] * inv[n - m] * inv[m];
}
};
//power函数切记强转成 Z !!!!!
constexpr int M = 1e9+7;
using Z = ModInt<M>;
void solve()
{
int n,k;
cin>>n>>k;
vector<vector<int>>g(n+1);
for(int i=1,u,v;i<=n-1;i++)
{
cin>>u>>v;
g[u].push_back(v);
g[v].push_back(u);
}
vector<int>a(n+1);
for(int i=1;i<=n;i++)
{
cin>>a[i];
}
vector<int>b(k);
for(int i=0;i<k;i++)
{
cin>>b[i];
}
vector<int>eor(n+1);
vector<vector<Z>>dp(n+1,vector<Z>(1<<4));
auto dfs=[&](auto &&self,int u,int fa)->void
{
eor[u]=a[u];
dp[u][0]=1;
for(auto v:g[u])
{
if(v!=fa)
{
self(self,v,u);
eor[u]^=eor[v];
vector<Z>ndp(1<<4);
//不切
for(int s=0;s<(1<<k);s++)
{
for(int t=0;t<(1<<k);t++)
{
ndp[s^t]+=dp[u][s]*dp[v][t];
}
}
//切
for(int s=0;s<(1<<k);s++)
{
for(int t=0;t<(1<<k);t++)
{
//节点v所在连通块的异或和
int res=eor[v];
for(int i=0;i<k;i++)
{
if(t>>i&1)
{
res^=b[i];
}
}
for(int i=0;i<k;i++)
{
if(res==b[i])
{
ndp[s^t^(1<<i)]+=dp[u][s]*dp[v][t];
}
}
}
}
dp[u]=ndp;
}
}
};
dfs(dfs,1,0);
Z ans=0;
for(int s=0;s<(1<<k);s++)
{
int res=eor[1];
for(int i=0;i<k;i++)
{
if(s>>i&1)
{
res^=b[i];
}
}
for(int i=0;i<k;i++)
{
if(res==b[i])
{
ans+=dp[1][s];
}
}
}
cout<<ans<<endl;
}
void init()
{
}
signed main()
{
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int t=1;
cin>>t;
init();
while(t--)
{
solve();
}
return 0;
}
首先,不难想到需要维护当前节点 u 的整棵子树异或和 eor[u]。那么不管在这棵子树里如何切割,子树中当前节点 u 所在的连通块的异或和必然满足 eor[u]^sum,其中 sum 为集合 b 能凑出来的异或和。这是因为当前被切下去的每个连通块的异或和必然为集合 b 的某个元素,所以这些连通块的异或和必然是集合 b 能凑出来的异或和。
因为集合 b 很小,所以考虑对其进行状态压缩,用状态 s 表示选取了哪几个元素生成异或和。之后,考虑定义 dp[u][s] 为当前来到节点 u,其子树中被切下去的部分的异或和,等于按照状态 s 生成的异或和。
对于当前边 (u,v),不删的话就有 ndp[u][s^t]+=dp[u][s]*dp[v][t],因为新产生的异或和直接等价于两状态的异或和。设当前状态 s 对应的异或和为 sum,那么只有当 eor[v]^sum,即节点 v 所在的连通块的异或和属于集合 b 时,才可以切掉这条边。设节点 v 所在连通块异或和为 res,那么就有转移 ndp[u][s^t^res]+=dp[u][s]*dp[v][t]。注意这里两个转移都是依赖的之前状态。
根据定义,若根节点是 1,那么只有当 eor[1]^sum 属于集合 b 时,当前方案才可以对答案产生贡献。由于存在双重循环枚举状态,所以复杂度是 O(n*4^k) 的,不做优化是过不了 F2 的()。
总结
其实补 F1 的时候就已经在往异或空间线性基想了,然后F2 点开题解也确实是,但最变态的就是这玩意儿还得用个没学过的算法优化,这是真补不了了......
还得练只能说,牛客寒假和学校的训练都结束了,是时候开始板刷蓝题+dp了!努力努力再努力,争取尽早上蓝!