F. 鸡数题!
第二类斯特林数
题意:有n个二进制位,m个数,每个数能获取至少1个二进制位,并且没有两个数的某一二进制位均为1。
等价于:将n个不同小球放到m个相同盒子,且盒子不能为空的方案数
所以答案即为第二类斯特林数
O(nlogn)
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define all(x) x.begin(),x.end()
#define no cout<<"No"<<endl
#define yes cout<<"Yes"<<endl
#define endl '\n'
// #define x first
// #define y second
typedef pair<int,int> PII;
const int N=200010;
const int mod=1e9+7;
const int INF=0x3f3f3f3f;
int ksm(int a,int n){
int r=1ll;
while(n){
if(n&1)r=r*a%mod;
a=a*a%mod;
n>>=1;
}
return r;
}
namespace binom {
int fac[N], ifac[N];
int __ = []
{
fac[0] = 1;
for (int i = 1; i <= N - 5; i++)
fac[i] = fac[i - 1] * i % mod;
ifac[N - 5] = ksm(fac[N - 5], mod - 2);
for (int i = N - 5; i; i--)
ifac[i - 1] = ifac[i] * i % mod;
return 0;
}();
inline int C(int n, int m)
{
if (n < m || m < 0)return 0;
return fac[n] * ifac[m] % mod * ifac[n - m] % mod;
}
inline int A(int n, int m)
{
if (n < m || m < 0)return 0;
return fac[n] * ifac[n - m] % mod;
}
}
using namespace binom;
void solve(){
int n,m;cin>>n>>m;
int sum=0;
for(int i=0;i<=m;i++){
sum=(sum+((m-i)%2?-1:1)*(ksm(i,n)*ifac[i]%mod*ifac[m-i]%mod)+mod)%mod;
}
cout<<sum<<endl;
}
signed main(){
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int _=1;
while(_--)solve();
return 0;
}
H. 01背包,但是bit
贪心/分类讨论/二进制:
关键:要枚举分界点
dp[j]表示:满足m的二进制表示下第j位为1,且背包的体积=m-(1<<j) 所能装的价值
但是这个dp数组实际还有一个状态没有表示到,就是当背包的体积为m的情况,再考虑一下这个即可
O(27n)
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define all(x) x.begin(),x.end()
#define no cout<<"No"<<endl
#define yes cout<<"Yes"<<endl
#define endl '\n'
// #define x first
// #define y second
typedef pair<int,int> PII;
const int N=200010;
const int mod=998244353;
const int INF=0x3f3f3f3f;
void solve(){
int n,m;cin>>n>>m;
vector<int>dp(30);
for(int i=0;i<n;i++){
int a,b;cin>>a>>b;
for(int j=26;j>=0;j--){
if((m>>j)&1){
if(b<(1ll<<j))dp[j]+=a;
else if(((b>>j)|((m>>j)-1))==(m>>j)-1)dp[j]+=a;
}
}
if((b|m)==m)dp[27]+=a;
}
int ans=0;
for(int i=0;i<=27;i++){
ans=max(ans,dp[i]);
}
cout<<ans<<endl;
}
signed main(){
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int _=1;
cin>>_;
while(_--)solve();
return 0;
}
K. 牛镇公务员考试
可以发现,一定会形成一个环,且环可能延申有边(这就是基环树)
且如果环上某一点延申边的话,则该边的方案数=该点的方案数
而该点的方案数=环的方案数
∴ ans=各个环的方案数的乘积
O(n)
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define all(x) x.begin(),x.end()
#define no cout<<"No"<<endl
#define yes cout<<"Yes"<<endl
#define endl '\n'
// #define x first
// #define y second
typedef pair<int,int> PII;
const int N=200010;
const int mod=998244353;
const int INF=0x3f3f3f3f;
void solve(){
int n;cin>>n;
vector<int>nx(n+1);
string s[n+1];
for(int i=1;i<=n;i++){
int a;cin>>a;
cin>>s[i];
nx[i]=a;
}
vector<int>book(n+1);
vector<int>v(n+1);
int ans=1;
for(int i=1;i<=n;i++){
int j=i;
while(!book[j]){book[j]=i;j=nx[j];}
if(book[j]==i){
int root=j;
int cnt=0;
for(int k=0;k<5;k++){
v[root]=k;
int t=s[root][k]-'A';
j=nx[root];
while(j!=root){
t=s[j][t]-'A';
j=nx[j];
}
if(t==k)cnt++;
}
ans=ans*cnt%mod;
}
}
cout<<ans<<endl;
}
signed main(){
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int _=1;
while(_--)solve();
return 0;
}
D. 数组成鸡
预处理/分治
由于M的范围是[-1e9,1e9],如果数组中>30个数的绝对值>=2,那么就不用考虑,因为2^30>1e9
所以要时刻保证数组中不能有30个绝对值超过2的数
做法:当n>=30时,枚举每个数,分别算出如果其是1/-1时数组所能得到的数,存进map里。
这样的数x不超过30个因为要满足 n > mp[x]+mp[x-2] > n-30 。反证:假设这样的数超过了30个,也就是说 n - (mp[x]+mp[x-2]) 一定> 30 ,矛盾。
2<=n<30时,直接让数组中的每个数加上[-1e5,1e5],因为 。
O(30*n)
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define all(x) x.begin(),x.end()
#define no cout<<"No"<<endl
#define yes cout<<"Yes"<<endl
#define endl '\n'
// #define x first
// #define y second
typedef pair<int,int> PII;
const int N=200010;
const int mod=998244353;
const int INF=0x3f3f3f3f;
unordered_map<int,int>mp;
unordered_map<int,int>cnt;
void solve(){
int n,q;cin>>n>>q;
set<int>st{0};
vector<int>v(n);
for(int i=0;i<n;i++){
cin>>v[i];
mp[v[i]]++;
}
cnt=mp;
if(n==1){
while(q--){
int a;cin>>a;
yes;
}
return;
}else if(n>=30){
for(auto [x,y]:cnt){
if(n-mp[x]-mp[x-2]>=30)continue;
int flag=1;
int sum=1;
for(int i=0;i<n;i++){
sum*=(v[i]-(x-1));
if(sum>1e9||sum<-1e9){flag=0;break;}
}
if(flag)st.insert(sum);
if(n-mp[x]-mp[x+2]>=30)continue;
flag=1;sum=1;
for(int i=0;i<n;i++){
sum*=(v[i]-(x+1));
if(sum>1e9||sum<-1e9){flag=0;break;}
}
if(flag)st.insert(sum);
}
}else{
sort(all(v));
for(int i=n-1;i>=0;i--)v[i]-=v[0];
for(int j=-1e5;j<=1e5;j++){
for(int i=0;i<n;i++){
int flag=1;
int sum=1;
for(int i=0;i<n;i++){
sum*=(v[i]-j);
if(sum>1e9||sum<-1e9){flag=0;break;}
}
if(flag)st.insert(sum);
}
}
reverse(all(v));
for(int i=n-1;i>=0;i--)v[i]-=v[0];
for(int j=-1e5;j<=1e5;j++){
for(int i=0;i<n;i++){
int flag=1;
int sum=1;
for(int i=0;i<n;i++){
sum*=(v[i]-j);
if(sum>1e9||sum<-1e9){flag=0;break;}
}
if(flag)st.insert(sum);
}
}
}
while(q--){
int a;cin>>a;
if(st.count(a))yes;
else no;
}
}
signed main(){
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int _=1;
while(_--)solve();
return 0;
}
J. 又鸟之亦心
二分
关键点:第 i 个任务结束时,一定有一个人在 𝑎𝑖 处
所以check时:用set记录某一个人(具体是谁不重要)在𝑎𝑖处,另一个人可能的位置集合,若某时刻set为空则false
初始时:某个人在a[0],另一个人(可能是x也可能是y)可能的位置是{x,y}
O(nlognlogn)
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define all(x) x.begin(),x.end()
#define no cout<<"No"<<endl
#define yes cout<<"Yes"<<endl
#define endl '\n'
// #define x first
// #define y second
typedef pair<int,int> PII;
const int N=200010;
const int mod=998244353;
const int INF=0x3f3f3f3f;
void solve(){
int n,x,y;cin>>n>>x>>y;
vector<int>a(n);
for(int i=0;i<n;i++)cin>>a[i];
function<int(int)> check=[&](int mid)->int{
set<int>st;
if(abs(x-y)<=mid){st.insert(x);st.insert(y);}
else return 0;
for(int i=0;i<n;i++){
if(i&&abs(a[i]-a[i-1])<=mid){
st.insert(a[i-1]);
}
while(st.size()&&*st.begin()<a[i]-mid){
st.erase(st.begin());
}
while(st.size()&&*st.rbegin()>a[i]+mid){
st.erase(*st.rbegin());
}
if(!st.size())return 0;
}
return 1;
};
int l=0,r=1e9;
while(l+1!=r){
int mid=l+r>>1;
if(check(mid))r=mid;
else l=mid;
}
cout<<r<<endl;
}
signed main(){
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int _=1;
while(_--)solve();
return 0;
}