Problem 1010. Bragging Dice
题意:
两个人玩传统的骰子游戏,现在追加三条规则:
1.如果之前没有人叫过1,则1被视为百搭;
2.如果手上的骰子点数都相同,则认为有额外的一个相同点数的骰子;
3.如果手上的骰子点数互不相同,则认为一个点数也没有。
现在双方知道对方的点数,问先手是否必胜。
题解:
只要先手能叫,必胜。因此,先手叫不了的情况为必败,否则必胜。
代码:
#include<iostream>
#include<algorithm>
#include<cstring>
#include<queue>
#define int long long
#define INF 0x3f3f3f3f3f3f3f3f
using namespace std;
const int N = 2e5 + 10;
const int mod = 1e9 + 7;
int n, a[N], b[N], c1[8], c2[8];
void solve(){
cin >> n;
for(int i = 1; i <= 6; i++) c1[i] = c2[i] = 0;
for(int i = 1; i <= n; i++) cin >> a[i];
for(int i = 1; i <= n; i++) cin >> b[i];
if(n > 6){
cout << "Win!\n";
return;
}
for(int i = 1; i <= n; i ++){
c1[a[i]] ++, c2[b[i]] ++;
if(c1[a[i]] > 1 || c2[b[i]] > 1){
cout << "Win!\n";
return;
}
}
cout << "Just a game of chance.\n";
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0); cout.tie(0);
int t = 1;
cin >> t;
while(t--){
solve();
}
}
Problem 1012.Buy Figurines
题意:
若干个人依次去买东西,每个人有一个购买时间。有若干个窗口,每个人到时会选择排队人最少的窗口,否则选择最左边的窗口。问需要多少时间买完。
题解:
模拟题,可以线段树维护。
代码:
#include<iostream>
#include<algorithm>
#include<queue>
#define int long long
#define lson (p << 1)
#define rson (p << 1 | 1)
using namespace std;
const int N = 2e5 + 10;
struct people{
int s, t;
bool operator < (const people &x) const{
return s < x.s;
}
};
struct shop{
int t, i;
bool operator < (const shop &x) const{
return t > x.t;
}
};
int n, m;
int time[N], tr[N << 2];
people a[N];
void build(int l = 1, int r = m, int p = 1){
tr[p] = 0;
if(l == r) return;
int mid = (l + r) >> 1;
build(l, mid, lson);
build(mid + 1, r, rson);
}
void update(int x, int y, int l = 1, int r = m, int p = 1){
if(l == r){
tr[p] += y;
return;
}
int mid = (l + r) >> 1;
if(x <= mid) update(x, y, l, mid, lson);
else update(x, y, mid + 1, r, rson);
tr[p] = min(tr[lson], tr[rson]);
}
int query(int l = 1, int r = m, int p = 1){
if(l == r) return l;
int mid = (l + r) >> 1;
if(tr[lson] <= tr[rson]) return query(l, mid, lson);
else return query(mid + 1, r , rson);
}
void solve(){
cin >> n >> m;
for(int i = 1; i <= m; i++) time[i] = 0;
build();
for(int i = 1; i <= n; i++){
cin >> a[i].s >> a[i].t;
}
sort(a + 1, a + 1 + n);
priority_queue<shop> q;
for(int i = 1; i <= n; i++){
while(!q.empty() && q.top().t <= a[i].s){
update(q.top().i, -1);
q.pop();
}
int tmp = query();
update(tmp, 1);
q.push({max(time[tmp] + a[i].t, a[i].s + a[i].t), tmp});
time[tmp] = max(time[tmp] + a[i].t, a[i].s + a[i].t);
}
int ans = 0;
for(int i = 1; i <= m; i++){
ans = max(ans, time[i]);
}
cout << ans << '\n';
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0); cout.tie(0);
int t;
cin >> t;
while(t--){
solve();
}
}
Problem 1003.Slipper
题意:
求s到t的最短路,其中任意两个点如果深度差等于k,则可以花费p来进行传送。
题解:
在满足深度差的层之间增加新的边,用dijstra跑最短路即可。
代码:
#include<iostream>
#include<set>
#include<cstring>
#include<queue>
#define int long long
#define INF 0x3f3f3f3f3f3f3f3f
using namespace std;
const int N = 1e6 + 10;
int n, top, s, t, k, p;
int head[N], dep[N], dis[N], vis[N], jump[N];
set<int> now[N];
struct edge{
int to, cost, next;
}e[N];
struct node{
int num, dis;
bool operator < (const node &x) const{
return dis > x.dis;
}
};
void init(){
top = 0;
memset(head, -1, sizeof(head));
memset(dis, INF, sizeof(dis));
memset(vis, 0, sizeof(vis));
memset(jump, 0, sizeof(jump));
for(int i = 1; i <= n; i++) now[i].clear();
}
void add(int u, int v, int w){
e[top].to = v;
e[top].cost = w;
e[top].next = head[u];
head[u] = top ++;
}
void dfs(int u, int fa){
now[dep[u]].insert(u);
for(int i = head[u]; ~i; i = e[i].next){
if(e[i].to == fa) continue;
dep[e[i].to] = dep[u] + 1;
dfs(e[i].to, u);
}
}
int dijkstra(){
priority_queue<node> q;
q.push({s, 0});
dis[s] = 0;
while(!q.empty()){
int u = q.top().num; q.pop();
if(vis[u]) continue;
vis[u] = 1;
if(u == t) return dis[t];
for(int i = head[u]; ~i; i = e[i].next){
int v = e[i].to, w = e[i].cost;
if(dis[v] > dis[u] + w){
dis[v] = dis[u] + w;
q.push({v, dis[v]});
}
}
if(jump[dep[u]]) continue;
jump[dep[u]] = 1;
for(auto v : now[dep[u] + k]){
if(dis[v] > dis[u] + p){
dis[v] =dis[u] + p;
q.push({v, dis[v]});
}
}
if(dep[u] - k <= 0) continue;
for(auto v : now[dep[u] - k]){
if(dis[v] > dis[u] + p){
dis[v] =dis[u] + p;
q.push({v, dis[v]});
}
}
}
return 0;
}
void solve(){
cin >> n;
init();
int u, v, w;
for(int i = 1; i < n; i++){
cin >> u >> v >> w;
add(u, v, w);
add(v, u, w);
}
cin >> k >> p >> s >> t;
dep[1] = 1;
dfs(1, 0);
cout << dijkstra() << '\n';
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0); cout.tie(0);
int t;
cin >> t;
while(t--){
solve();
}
}
Problem 1007.Count Set
题意:
给定一个排列,要求计算有多少种取法,取长度为k的子排列,使得以子排列为下标的原排列与该子排列的交集为空。
题解:
假设有一个n个点的图,排列p中的某个项pi表示从点i到点p有一条边。则可以发现这是一个由若干不交有向环组成的图。
则题目中的条件可以等价于,从每个环中挑选若干个点,使得每个环中被选出的点不相邻,从一个大小为m的环中选出k个点的方案数是(我当时赛场上推的是),因为可以把被选出的点当作大小为2的段,然后不选的点当作大小为1的段,用大小为2保证被选的点不相邻,问题就转化成总共有m - k个块,挑出k个块的方案数,如果在序列上,这就是一个组合数,在环上也只需要挑一个位置拆开,变成序列问题,然后分有没有大小为2的块恰好在断开处讨论一下,发现就是上面的式子。
推出这个式子之后只需要列出每个环的生成函数然后 NTT 合并就好了 。
代码:
#include<iostream>
#include<iomanip>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<vector>
#include<string>
#include<map>
#include<set>
#include<queue>
using namespace std;
#define debug(x) cout<<#x<<": "<<(x)<<endl
#define debug2(x,y) cout<<#x<<":"<<endl;for(int i=1;i<=y;++i)cout<<x[i]<<" ";cout<<endl
#define mem(x,y) memset(x,y,sizeof(x));
#define int long long
#define double long double
#define lson (p<<1)
#define rson (p<<1|1)
const int inf=0x3f3f3f3f3f3f3f3f;
const double eps=1e-9;
const double PI=acos(-1.0);
const int maxn=2e6+10;
const int mod=998244353;//1e9+7
const int G=3,GI=332748118;//5
int prt[maxn][2];
int invlen[maxn];
int n,k;
int a[maxn];
bool vis[maxn];
int tot=0,ff[maxn],ll[maxn<<2],rr[maxn<<2],sum[maxn];
int fac[maxn],inv[maxn];
int A[maxn],B[maxn],res[maxn];
int rev[maxn];
int bitpow(int x,int y)
{
int res=1;
while(y)
{
if(y&1)res=res*x%mod;
x=x*x%mod;
y>>=1;
}
return res;
}
void init(int len)
{
for(int i=1;i<len;++i)
{
rev[i]=rev[i>>1]>>1;
if(i&1)rev[i]|=len>>1;
}
}
void NTT(int *f,int len,int flg)
{
for(int i=0;i<len;++i)
{
if(i<rev[i])
swap(f[i],f[rev[i]]);
}
for(int l=2;l<=len;l<<=1)
{
int gn=prt[l][(int)(flg==1)];
for(int j=0;j<len;j+=l)
{
int g=1;
for(int k=j;k<j+l/2;++k)
{
int x=f[k];
int y=g*f[k+l/2]%mod;
f[k]=(x+y)%mod;
f[k+l/2]=(x-y+mod)%mod;
g=g*gn%mod;
}
}
}
if(flg==-1)
{
int inv=invlen[len];
for(int i=0;i<len;++i)
f[i]=f[i]*inv%mod;
}
}
void initinit()
{
fac[0]=1;
for(int i=1;i<maxn;++i)
fac[i]=fac[i-1]*i%mod;
inv[maxn-1]=bitpow(fac[maxn-1],mod-2);
for(int i=maxn-2;i>=1;--i)
inv[i]=inv[i+1]*(i+1)%mod;
inv[0]=1;
for(int i=2;i<maxn;i<<=1)
{
prt[i][1]=bitpow(G,(mod-1)/i);
prt[i][0]=bitpow(GI,(mod-1)/i);
invlen[i]=bitpow(i,mod-2);
}
}
int C(int x,int y)
{
if(x<y)return 0;
return fac[x]*inv[y]%mod*inv[x-y]%mod;
}
void get(int ii,int siz)
{
for(int i=0;i<=siz/2;++i)
{
ff[tot++]=(2*C(siz-i,i)%mod-C(siz-i-1,i)+mod)%mod;
}
sum[ii]=tot-1;
}
void solve(int l,int r,int p)
{
if(l==r)
{
ll[p]=sum[l-1]+1;
rr[p]=sum[l];
return;
}
int mid=(l+r)>>1;
solve(l,mid,lson);
solve(mid+1,r,rson);
ll[p]=ll[lson];rr[p]=rr[rson];
int len=1;
while(len<rr[p]-ll[p]+1)len<<=1;
init(len);
for(int i=0;i<len;++i)
A[i]=B[i]=res[i]=0;
for(int i=ll[lson];i<=rr[lson];++i)
A[i-ll[lson]]=ff[i];
for(int i=ll[rson];i<=rr[rson];++i)
B[i-ll[rson]]=ff[i];
NTT(A,len,1);NTT(B,len,1);
for(int i=0;i<len;++i)
res[i]=(A[i]*B[i]%mod+mod)%mod;
NTT(res,len,-1);
for(int i=ll[p];i<=rr[p];++i)
ff[i]=res[i-ll[p]];
// for(int i=ll[p];i<=rr[p];++i)
// cout<<f[i]<<" ";
// cout<<'\n';
}
void work()
{
tot=0;
cin>>n>>k;
for(int i=1;i<=n;++i)
vis[i]=0;
for(int i=1;i<=n;++i)
cin>>a[i];
int cntcir=0;
sum[0]=-1;
for(int i=1;i<=n;++i)
{
if(vis[i])continue;
vis[i]=1;
int now=i,cnt=1;
while(vis[a[now]]==0)
{
now=a[now];
++cnt;
vis[now]=1;
}
if(cnt==1)continue;
get(++cntcir,cnt);
}
if(cntcir==0)
{
if(k)cout<<0<<'\n';
else cout<<1<<'\n';
return;
}
solve(1,cntcir,1);
cout<<ff[k]<<'\n';
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
// cout<<fixed<<setprecision(6);
initinit();
int _=1;
cin>>_;
while(_--)
work();
return 0;
}
Problem 1006. BBQ
题意:
给定一个字符串,问最少的操作次数,使得操作后的字符串每四个一队都是ABBA的形式。
代码:
#include<stdio.h>
#include<string.h>
#include<random>
#define U unsigned
#define LL long long
#define UL U LL
int t[10];
int g[10][5];
char w[3000000];
void dfs(int n,int c,int idx)
{
int m=9999999;
if(n)
for(int a=1;a<=7;a++)
for(int b=1;b<=7;b++)
{
int p[5]={0,a,b,b,a};
memset(g,1,sizeof g);
for(int i=0;i<=4;i++)
g[0][i]=i;
for(int i=0;i<=7;i++)
g[i][0]=i;
for(int i=1;i<=n;i++)
for(int j=1;j<=4;j++)
g[i][j]=std::min(std::min(g[i-1][j]+1,g[i-1][j-1]+(t[i]!=p[j])),g[i][j-1]+1);
if(g[n][4]<m)m=g[n][4];
}
if(n)w[idx]=m;
if(n==7)return;
n++;
for(int i=1;i<=c;i++)
{
t[n]=i;
dfs(n,c,idx*8+i);
}
t[n]=c+1;
dfs(n,c+1,idx*8+c+1);
}
char s[1000001];
int dp[1000001];
int last[26];
int pre[1000001];
void sol()
{
scanf("%s",s+1);
int n=strlen(s+1);
memset(dp+1,10,n*4);
memset(last,-1,sizeof last);
for(int i=1;i<=n;i++)
{
pre[i]=last[s[i]-'a'];
last[s[i]-'a']=i;
}
for(int i=0;i<n;i++)
{
dp[i+1]=std::min(dp[i+1],dp[i]+1);
int cnt=0;
int idx=0;
int tmp[8];
for(int j=1;j<=7&&i+j<=n;j++)
{
//int c=s[i+j]-'a';
if(pre[i+j]<=i)
idx=idx*8+ (tmp[j]=++cnt);
else
idx=idx*8+ (tmp[j]=tmp[pre[i+j]-i]);
dp[i+j]=std::min(dp[i+j],dp[i]+w[idx]);
}
}
printf("%d\n",dp[n]);
}
int main()
{
dfs(0,0,0);
int t;
scanf("%d",&t);
while(t--)sol();
}
Problem 1002. Jo loves counting
题意:
对于一个数,d为它的一个因数且这个数所有的质因数也是d的因数,随机选择一个满足的d,如果d等于这个数本身,则获得d分,否则0分。现在给定一个M,问在1 ~ M中选一个数,获得的分数的期望。
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
#define de(x) cout << #x <<" = "<<x<<endl
#define dd(x) cout << #x <<" = "<<x<<" "
const ll P=29ll<<57|1;
const int Lim=1e6, MAXN=Lim+10;
int fc[MAXN], prime[MAXN/10], cntprime;
ll inv[64];
inline ll mul(ll a,ll b,ll p=P) {
return (a*b-(ull)((long double)a/p*b)*p+p)%p;
}
inline ll kpow(ll a, ll x) {
ll ans=1;
for(;x;x>>=1, a=mul(a, a))
if(x&1) ans=mul(ans, a);
return ans;
}
inline ll h(ll p, int k, ll pk) {
return P-mul(pk, mul(inv[k], inv[k-1]));
}
inline ll PN_sieve(const ll n, const int flr, const ll hd) {
ll res=mul(mul(n, mul(n+1, inv[2])), hd);
for(int i=flr+1; i<=cntprime; ++i) {
int p=prime[i], k=1;
ll val=n/p, pk=p;
if(val<p)
break;
while(val>=p) {
val/=p;
pk*=p;
++k;
res+=PN_sieve(val, i, mul(hd, h(p, k, pk)));
if(res>=P)
res-=P;
}
}
return res;
}
inline void sieve() {
for(int i=2; i<=Lim; ++i) {
if(!fc[i]) fc[i]=prime[++cntprime]=i;
for(int j=1; j<=cntprime; ++j)
if(prime[j]>fc[i]||prime[j]*i>Lim)
break;
else
fc[prime[j]*i]=prime[j];
}
}
inline void init() {
sieve();
for(int i=1; i<64; ++i)
inv[i]=kpow(i, P-2);
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0); cout.tie(0);
init();
int T; cin>>T;
ll M;
while(T--) {
cin>>M;
cout<<mul(PN_sieve(M, 0, 1), kpow(M, P-2))<<"\n";
}
cout.flush();
return 0;
}