1 String
problem
定义( ext{inf}(s)=sss...),即无限循环。求( ext{inf}(a))和( ext{inf}(b))大小关系。
solution
比较(a+b)和(b+a)。
thoughts
话说这个数据范围有一个好大的提示,后知后觉。
(sum(|a|+|b|)le 10^6),这个好像提醒了我们要加起来。
想不到想不到。跑了。
code
过于显然。不写了。
update:写上吧。
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <iostream>
using namespace std;
long long read(){
long long a=0,op=1;char c=getchar();
while(c<'0'||c>'9') {if(c=='-') op=-1;c=getchar();}
while(c>='0'&&c<='9') {a*=10,a+=c^48,c=getchar();}
return a*op;
}
int t;
string a,b;
int main() {
t=read();
while(t--) {
cin>>a>>b;
if(a+b>b+a) puts(">");
else if(a+b<b+a) puts("<");
else puts("=");
}
return 0;
}
2 Structure
problem
给定(n,m),询问有多少种序列满足(sumlimits_{i=1}^nsqrt{x_i}=sqrt{m})
solution
先化简成最简根号形式(ksqrt{b}),然后转化为不定方程(x_1+x_2+...+x_n=k)的解的个数。答案是(C_{n+k-1}^n)
thoughts
最想切的一个题。但是因为组合数知识不好,组合数式子写错了。改一下就80了。
0pts:组合数写错了。写了个(C_{n+k-1}^{k}),然而因为样例太水检查不出来。
80pts:和std一样RE了。不知道该咋办。
我求组合数的方法是线性阶乘和线性逆元,最后再乘起来。注意分子上的不用%。
std直接快速幂求逆元,应该都可以,复杂度都很优秀。
为了降低复杂度也是煞费苦心,组合数的递推显然毫无用处,数组都开不下。
数据出锅了?不怪我了哈哈哈哈
update:好吧std出锅了。我对了。组合数推对了吼吼吼吼
code
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <iostream>
using namespace std;
long long read(){
long long a=0,op=1;char c=getchar();
while(c<'0'||c>'9') {if(c=='-') op=-1;c=getchar();}
while(c>='0'&&c<='9') {a*=10,a+=c^48,c=getchar();}
return a*op;
}
const int maxn=1e6+10,maxm=1e4+10,mod=1e9+7,ppp=4e4+10;
long long n,m,cnt;long long x,y;
long long pingfang[maxn],yinzi[maxn];
long long tot;
long long jiecheng[maxn],inv[maxn];
void init(){
n=read(),m=read();
for(long long i=1;i<=ppp;i++) pingfang[++tot]=i*i;
for(long long i=1;i<=tot&&pingfang[i]<=m;i++){
if(m%pingfang[i]==0) yinzi[++cnt]=sqrt(pingfang[i]);
}
}
long long c(long long x,long long y){return jiecheng[x]*inv[y]%mod*inv[x-y]%mod;}
int main(){
//freopen("structure.in","r",stdin);
//freopen("structure.out","w",stdout);
init();
x=n+yinzi[cnt]-1,y=yinzi[cnt];
//printf("%lld %lld
",x,y);
inv[1]=inv[0]=1,jiecheng[0]=1;
for(long long i=2;i<=x;i++) inv[i]=1ll*(mod-mod/i)*inv[mod%i]%mod;
for(long long i=1;i<=x;i++) jiecheng[i]=1ll*i*jiecheng[i-1]%mod,inv[i]=1ll*inv[i]*inv[i-1]%mod;
printf("%lld
",c(x,y));
return 0;
}
3 Word
problem
Statement
每个单词有自己的词性,每个句子可以固定由几种词性的单词按顺序连起来,就算翻译起来可能很奇怪,但是语法是正确的。方方方重新创造了词典和语法,每个单词定义了若干种词性,同时他定义了某种句子只能由某些词性的单词按顺序组成。一个单词在句子中所表示的词性不同的时候即认为是一种不同的解释方法。
有一天它写下了一串不含空格的字符串递给圆圆圆,但是由于这一串内容并没有上下文联系,所以圆圆圆想问你这个内容有多少种解释方式。由于这个数字过大,所以只需要你求出答案对 (1000000007) 取模之后的结果。
Input&Output Format
第 1 行 2 个正整数 (n) 和 (m),表示单词数量和语法数量。
接下来 (n) 行,每行一个单词 (s_i) 和整数 (k_i) ,表示这个单词以及它的词性数量,之后跟了(k_i) 个整数 (a_j) ,表示该单词可以表示的词性编号。 (s_i) 相同的单词视为同一个单词,它所表示的词性可以合并。
接下来 (m) 行描述语法,首先一个正整数 (p_i) ,表示当前语法对应的句子包含的单词数。之后 (p_i)个数(b_j)分别表示每个单词的词性编号。
最后一行输入字符串 (t),表示方方方写下的内容。
答案只有一行,表示答案对 (1000000007) 取模之后的结果。
Data Convention
另有 10%的数据,(1≤n≤20),所有 (s_i) 都只包含一种字母。
对于 100%的数据,(1≤n≤5000,1≤m,k_i,a_j,p_i,b_j ≤10,1≤| s_i|≤20,1≤|t |≤1000)。
solution
利用 dp
进行计数,设 dp[i][j][k]
表示匹配到母串的第 (i) 个字符的时候,处于第 (j) 个句子的组成成分中第 k 个成分的方案数,明显的转移方程:
dp[i][j][k]=∑dp[t][j][k-1] (s[t+1...i]是词典上的单词,并且这个单词的词性能够匹配第 j 个句子的第 k 个成分)
由于单词长度不超过 20,直接枚举 20 位,每次遍历一遍词典进行匹配即可。
根据设置的状态,需要每次将 dp[i][j][size[j]]
的答案统计到 dp[i][0][0]
上,因为这部分已经能够匹配整个完整的句子,之后为了方便统计,再将 dp[i][0][0]
的答案发散到dp[i][j][0]
上。
初态:dp[0][0][0]=1
答案:dp[|t|][0][0]
(t
是给出的母串)
总复杂度:每次匹配成功再进行状态的转移,匹配的总复杂度是 (O(|t||s|N)),成功匹配的次数是$ O(|t||s|)$,每次匹配成功后的转移复杂度为 (O(pm)),所以总体复杂度为(O(|t||s|N+|t||s|pm))
thoughts
考场上没想做这个题的。虽然说subtask有一个很可做,代码量太大了得不偿失。去检查别的去了。
code
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define endl '
'
#define il inline
const int maxn=5005;
int trie[100005][26];
int tot;
il int ins(char *s)
{
int p=1,i=0;
while(s[i])
{
int nxt=s[i]-'a';
if(!trie[p][nxt]) trie[p][nxt]=++tot;
p=trie[p][nxt];
++i;
}
return p;
}
set<int>st[100005];
char s[maxn];
ll dp[1005][11][11];
int a[11][11];
int siz[11];
const ll mod=1e9+7;
int main()
{
//ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
freopen("word.in","r",stdin);
freopen("word.out","w",stdout);
tot=1;
int n,m;
cin>>n>>m;
for(int i=1;i<=n;i++)
{
cin>>s;
int id=ins(s);
int kk;
cin>>kk;
for(int j=1;j<=kk;j++)
{
int tp;
cin>>tp;
st[id].insert(tp);
}
}
for(int i=1;i<=m;i++)
{
int kk;
cin>>kk;
for(int j=1;j<=kk;j++)
cin>>a[i][j];
siz[i]=kk;
}
cin>>s+1;
int sz=strlen(s+1);
dp[0][0][0]=1;
for(int i=0;i<=sz;i++)
{
for(int j=1;j<=m;j++)
{
if(!dp[i][j][siz[j]]) continue;
dp[i][0][0]+=dp[i][j][siz[j]];
dp[i][0][0]%=mod;
}
if(i==sz) break;
for(int j=1;j<=m;j++)
dp[i][j][0]=dp[i][0][0];
for(int j=1;j<=m;j++)
{
for(int k=0;k<siz[j];k++)
{
if(!dp[i][j][k]) continue;
int dd=1,now=1;
while(1)
{
if(trie[now][s[i+dd]-'a']) now=trie[now][s[i+dd]-'a'];
else break;
if(st[now].find(a[j][k+1])!=st[now].end())
{
dp[i+dd][j][k+1]+=dp[i][j][k];
dp[i+dd][j][k+1]%=mod;
}
dd++;
}
}
}
}
cout<<dp[sz][0][0]<<endl;
}
4 Hp
又名:lzw用极其错误的方法骗来了一大半的分
problem
(n)个人,打每个人消耗(a_i)体力,打完之后会有(b_i)的恢复值,任意时刻体力值需要大于0,每个人只能打一次或不打。可以按照任意顺序打,对于(q)次询问的初始体力(c_i),询问最多能打多少人。
solution
⾸先注意到训练师可以分成两类,第⼀类是 a b ,第⼆类是 a b 。
对于第⼀类⼈,按照 a 的值从⼩到⼤排序,然后贪⼼做即可。
对于第⼆类⼈,我们不妨反过来考虑,即给出⼀个最终 hp 值,然后 每次减 b 加 a 倒着推出⼀个中间 hp 值。那么问题就转换成,给出最终 hp 值,每次减 b 加 a,求在战胜了 k 个⼈后的 hp 值最⼩能是多少。
考虑这样⼀个贪⼼过程:给出最终 hp 后,我们把所有可以挑战的⼈(即满⾜b⼩于当前 hp值的⼈)按照(a-b)的⼤⼩放进⼀个优先队列,每次取(a-b)最⼩的⼀个⼈进⾏挑战,这之后再次把新的可以挑战的⼈加⼊优先队列。这个策略的意义相当于:每次增加尽可能少的 hp值来多挑战⼀个⼈。
因此我们只要枚举最终 hp 值就能得到所有可能的结果。注意到当最终 hp 值⼤于 (max{a_i,b_i})时,按照上述策略生成的挑战顺序不会再改变,因此最终 hp 值只需要枚举到 1000 即可。
总复杂度(O(max{A,B}(nlog n+T)))。
接下来说明为什么通过枚举 hp 值再按照进⾏上述贪⼼过程⼀定能得到最优解。
记初始 hp 值为 (h),通过 (h) 生成的策略集(即挑战⼈的顺序)为 (T(h)) ,倒着挑战到第 (i) 个⼈时的策略集为(T_i(h)),通过这个策略集算出来的(中间)⾎量记为 (F(T_i(h)))。
如果通过上述策略算出挑战 i 个⼈时所需的最⼩(中间)⾎量 (m_i) 不是最优解,那就是说存在 (k< i),在第(k) 步时不应选使⾎量增加最⼩的⼈挑战,⽽应选择⼀个使⾎量增加更⼤的⼈挑战。这意味着在这⼀步时通过挑战⾎量增加更⼤的⼈,来更早的调整可选策略,使得能更早的选择变化值较⼩的⼈,从⽽使得后⾯的结果更优。
不妨设在这个所谓第 (k) 步应⾄少增加 (k_d) 的⾎量才能调整可选策略,⽽此时最⼩的⾎量增加值为(e_k(0<e_k<d_k))
那么我们考虑(h_0+1)对在第 (k) 步的决策产⽣的变化,分为如下⼏种情况:
(1) (T_k(h_0)=T_k(h_0+1)):这意味着前 (k-1) 步 hp 增加值不变,且 (e_k)也不变,可选策略也不变,但初始 hp 值变⼤,因此这时的(d_k')变成(d_k'=d_k-1) ,则 (e_k) 更加接近(d_k'),这是我们希望看到的(因为这样就能不断逼近并最终产⽣“增加 (e_k)恰好加⼊更优决策”这样的结果)。
(2)(T_k(h_0)
eq T_k(h_0+1)):即前 (k-1) 步决策发⽣变化,并最终使得“⾛完前 (k-1) 步后的 hp 值”以
及 (e_k) 发⽣变化。⾸先要注意到发⽣变化后(e_k'le e_k)。⽽对于“⾛完前 (k-1) 步后的 hp 值”发⽣的变化⼜分为如下⼏种:
“⾛完前(k-1) 步后的 hp 值”变⼤。如果变⼤后的 hp 值没有超过第 k 步原本的界限,那我们仍然可以通过(h_0+2)来继续逼近,否则我们就倒退回恰好没有超过第 k 步原本的界限的第(j(j<k))步决策,然后再进⾏逼近(其实就是⽤数学归纳法证明,先证明⼩于 (k) 步时结论成⽴(即可以进⾏逼近),再证第 k 步时结论亦成⽴)。
“⾛完前(k-1)步后的 hp 值”不变,则有(e_k'le e_k<d_k=d_k'),于是亦可以进⾏逼近。
“⾛完前(k-1) 步后的 hp 值”变⼩,这时有(e_k'le_k<d_k<d_k'),因此我们仍然可以继续逼近。
以上说明,即便对于某个最终⾎量,存在贪⼼策略的反例,我们仍然可以通过调整最终⾎量的来囊括最优解,因此这样的做法是正确的。
thoughts
有思路,因为代码难度放弃了。转战部分分成功。
20pts:判断最弱的是否能够战胜。由于贡献非负,要不然就一个都打不下来,要不然就都能打。
40pts:贪心。先把所有能吃的贡献非负的都吃了。按照先HP后贡献的顺序从小到大。之后在按照HP从大到小,我先打能打的最大的人会比后打好,因为现在打的贡献都为负,体力都会消耗。所以越往后剩下要求越低的是更好的选择。感性理解是这样吧,具体也不太会证明。
Subtask Code
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <iostream>
using namespace std;
long long read(){
long long a=0,op=1;char c=getchar();
while(c<'0'||c>'9') {if(c=='-') op=-1;c=getchar();}
while(c>='0'&&c<='9') {a*=10,a+=c^48,c=getchar();}
return a*op;
}
#define INF 0x3f3f3f3f
long long n,q,c,pos,head,maxx=-INF,max_pos;
const int maxn=1e3+10;
struct node{long long a,b,seq;}t[maxn];
long long ener[maxn],tmp[maxn];
bool flag1=1;
bool cmp1(node x,node y){return x.a<y.a;}
bool cmp2(node x,node y){if(x.a==y.a) return x.seq>y.seq;return x.a<y.a;}
bool cmp3(node x,node y){if(x.seq==y.seq) return x.a>y.a;return x.seq>y.seq;}
void subtask1(){
sort(t+1,t+n+1,cmp1);
while(q--){
c=read();
if(t[1].a<c) printf("%lld
",n);
else printf("0
");
}
}
void subtask2(){
while(q--){
c=read();
sort(t+1,t+n+1,cmp2);
if(c<=t[1].a) {printf("0
");continue;}
else{
head=0;
for(int i=1;i<=n;i++){
if(t[i].seq>=0&&c>t[i].a) head++,c+=t[i].seq;
}
sort(t+1,t+n+1,cmp3);
for(int i=1;i<=n;i++){
if(t[i].seq<0&&c>t[i].a) head++,c+=t[i].seq;
}
printf("%lld
",head);
}
}
}
int main(){
freopen("hp.in","r",stdin);
freopen("hp.out","w",stdout);
n=read();
for(long long i=1;i<=n;i++){
t[i].a=read(),t[i].b=read(),t[i].seq=t[i].b-t[i].a;
if(t[i].a>t[i].b) flag1=0;
}
q=read();
if(flag1) subtask1();
else subtask2();
return 0;
}